Loading...
7/13/2025
ABC414、ミラティブプログラミングコンテストです。Mirrativといえば、ぼくが中学生の頃(なので8か9年ぐらい前?)にお世話になっていたサービスなので、まさかこんなところでまた巡り合うとは思っておらずとても驚くなどしていました。
それとは別でABC414は出るかどうかかなり悩みました。しばらくは海外コンテストも含めて継続して出ていたのですが、先週土曜日のCF以降あまり乗り気にならず、ABCも同様だったんですよね。精進自体はちょっとしていましたが1。そんな感じで始まったABC414です。相変わらず録画もあります。
結果としては遅めの5完。これで青perfが出るんだぁという不思議な気持ち。
が以下かつが以上であることが、配信をすべて見られる条件です。そのように実装すればよいです。提出
use proconio::input; fn main() { input!{ n: usize, l: u64, r: u64, v: [(u64,u64); n], } let ans = v.iter() .filter(|&(x,y)| x <= &l && &r <= y) .count(); println!("{}", ans); }
何も考えずに適当に「包含判定をすればいいですね~」と言いながら書き始めたら条件が全然あっておらず、サンプルも通らずで困惑していました。水色コーダーさん、その問題A問題ですよ?
要約問題文ありがたいです。RLEを復元しつつ、長さが100を超えた時点で打ち切ってToo Long
を出力すればよいです。同じ文字を繰り返すときはstd::iter::repeat
が便利でいいですね2。
本当はfor文を使わずにきれいに書きたいところですが、合計の長さが最大でと少し大きすぎるため、手続き的に書くことに。提出
use std::iter::repeat; use itertools::Itertools; use proconio::input; fn main() { input!{ n: usize, c: [(char, usize); n], } let mut ans = vec![]; let mut count = 0; for (v, c) in c.iter() { if count + *c > 100 { println!("Too Long"); return; } count += *c; ans.push(repeat(*v).take(*c).collect::<String>()); } println!("{}", ans.iter().join("")); }
なんか難しかったっぽい?確かに実装方針で沼った部分もありましたが、特別難しいとは感じなかったので不思議です。
回文なので、文字列の前半が決まると後半としてあり得るものは2つに絞られます。例えば前半として123
とした場合は、後半として21
を付け足した12321
か、321
を付け足した123321
の2択です。これを考えると、の最大ケースでも前半部分としてありうるものは高々通りですから、以下で十進数表記が回文となるものを全列挙することが可能です。
値に対する進数変換/回文判定はそれぞれでできます。結局、を「の前半部分としてありうるものの最大値」として3、全体で時間でこの問題を解くことができます。前述の通りなので、十分高速です。提出
use proconio::input; static M : usize = 1000001; fn main() { input!{ a: usize, n: usize, } let mut v = vec![]; for i in 1..=n.min(M) { let mut k = i; let mut vv = vec![]; while k > 0 { vv.push(k%10); k /= 10; } let mut k1 = i; for x in vv.iter() { k1 *= 10; k1 += x; } let res1 = k1.to_string(); if k1 <= n && res1.chars().eq(res1.chars().rev()) { v.push(k1); } let mut k1 = i; for x in vv[1..].iter() { k1 *= 10; k1 += x; } let res1 = k1.to_string(); if k1 <= n && res1.chars().eq(res1.chars().rev()) { v.push(k1); } } let ans = v.iter() .filter(|x| { let mut k = **x; let mut v = vec![]; while k > 0 { v.push(k%a); k /= a; } v.iter().eq(v.iter().rev()) }) .sum::<usize>(); println!("{}", ans); }
なお、提出版では何を考えたのかをに設定しているせいで1380msもかかっています。あぶねぇ。
電波強度をすべて同じに設定して個以下で全体を被覆するかを判定すれば良いですね~と、うきうきで二分探索を実装しますがサンプル3が合いません。あれ?おかしいな...
よく見ると誤読をしています。電波強度は別々に設定できて、その最小値なんですか。はぁ。なんですかこれは?
とりあえず落ち着いて考えると、ソートしてdedupした上でどの区間を取るか?という話になりそうです。すると、番目まで見て回取らない選択をしたときの和の最小値、という自明なdpが生えます...が、これはです。間に合いません。
ところで、これは区間を取るための条件が存在しないため、大きい方から貪欲に選べば最適になります4。未証明ですが、ACをすれば証明なので気にしないことにします。とにかく問題が解けました。実装が微妙に合いません。気合で合わせます。提出
use itertools::Itertools; use proconio::input; fn main() { input!{ n: usize, m: usize, mut a: [u64;n] } let v = a.iter() .sorted() .dedup() .tuple_windows() .map(|(ai,aj)| { aj-ai }) .sorted() .collect_vec(); if v.len() < m { println!("0"); } else { println!("{}", v[..=v.len()-m].iter().sum::<u64>()); }; }
う~~~ん、見た目のなんですかこれは?感がすごいです。取り敢えず考察をすると、を固定してしまえばに対するが一意に定まるので、を固定することを考えます。
固定されたに対してが取りうる値の個数は、なるであって、をで割ったあまりが0ではないものの個数です。逆に、そのようなであってで割ったあまりが0になるものの個数はですから、求めたい値の個数は個とわかります。しかし、このままではかかってしまうので高速化する必要があります。
ここで、求めたい値の個数の式を眺めると、の値が同じであればまとめて計算できそうな気がします。自体は(を変数としてみると)広義単調減少な関数なので、二分探索を用いることでがおなじになるようなの最大値は簡単に見つかります5。
最小値を,最大値をとすると、数えたい組み合わせはと計算できます。適当に式変形を行うと、で、これはで計算することができます。全体では、として取りうる値の個数だけこの計算を行う必要があります。
以下は聞きかじりなので「こいつは聞いたことをあたかも自分が考えたかのように言ってるな~」と思いながら読んでほしいです。
関数とする。
よって、全体としてが取りうる値の個数は個以下となる。
以上から、全体で時間でこの問題を解くことができました。二分探索分のは落とせるらしいですが、まあRustは高速な言語なので良いでしょう。提出
use ac_library::ModInt998244353 as Mint; use proconio::input; fn sum(n: Mint) -> Mint { n * (n+1) / 2 } fn main() { input!{ n: u64, } let mut ans = Mint::new(0); let mut b = 2; while b <= n { let zc = n/b; let mut ok = b; let mut ng = n+1; while ok.abs_diff(ng) > 1 { let mid = (ok+ng)/2; if n/mid == zc { ok = mid; } else { ng = mid; } } // [b, ok] までの個数を数え上げ ans += Mint::new(ok-b+1) * (n + 1 - zc); ans -= sum(Mint::new(ok)) - sum(Mint::new(b-1)); b = ok+1; } println!("{}", ans); }