記録
4完, 321人中76位でした (perf.1615, レート 1711 -> 1701) 解くのが遅いのもそうですが, Bの3ペナが辛いですね…
続きを読む聴いてください、バグループ
バグってない平方分割を知らない
バグってない平方分割が実装できない
バグってない平方分割を知らない
バグってない平方分割が実装できないよ
蟻本 pp169-170 を参考にして, 自分なりに平方分割を実装してみた.
Library Checker - Static RMQ Library Checker
区間最小値はセグメント木でも行けるが, 平方分割でも行ける.
#include<bits/stdc++.h> using namespace std; int main(){ int n,q; cin >> n >> q; int b = int(pow(n, 0.5) + 1); vector<int> a(n); for (int i=0; i<n; i++){ cin >> a[i]; } vector<int> dat(b, 1e9); for (int i=0; i<b; i++){ for (int j=min(n, i*b); j<min(n, (i+1)*b); j++){ dat[i] = min(dat[i], a[j]); } } for (int num=0; num<q; num++){ int l, r; cin >> l >> r; int tl = l+b-(l%b), tr = r-(r%b), ans = 1e9; for (int i=l; i<min(r,tl); i++){ ans = min(ans, a[i]); } for (int i=tl/b; i<tr/b; i++){ ans = min(ans, dat[i]); } for (int i=max(tl,tr); i<r; i++){ ans = min(ans, a[i]); } cout << ans << endl; } }
細かくみていく.
int b = int(pow(n, 0.5) + 1);
bはバケットのサイズでもあるが, バケットの数でもある. +1でうまい具合に調整してある.
vector<int> dat(b, 1e9); for (int i=0; i<b; i++){ for (int j=min(n, i*b); j<min(n, (i+1)*b); j++){ dat[i] = min(dat[i], a[j]); } }
前処理はこんな感じ. 各バケットについて計算している. ここは
(追記 2022/09/03)for文だと必要ないが, sortだとバケットの左端jを i*b としてしまうと配列外にアクセスしてしまう. これを防ぐために, ここの時点で for int j min(n, i*b) と書いている.
for (int num=0; num<q; num++){ int l, r; cin >> l >> r; int tl = l+b-(l%b), tr = r-(r%b), ans = 1e9; // (1) for (int i=l; i<min(r,tl); i++){ ans = min(ans, a[i]); } // (2) for (int i=tl/b; i<tr/b; i++){ ans = min(ans, dat[i]); } // (3) for (int i=max(tl,tr); i<r; i++){ ans = min(ans, a[i]); } cout << ans << endl; }
ここはクエリごとの処理. ここは. 蟻本と違うのは, 左から順番に見ていることと, while文を使っていないこと.
tl は, l から右に行って最も近いブロックを示し, tr は, r から左に行って最も近いブロックを示す.
蟻本でも tl, tr を使っているが, ちょっとだけ違う. (1) と (3) でそれぞれforの区間にmin, maxを使っている. これは下図のような状態を防ぐためである. 余計なものまで計算してしまい正しい結果が出ない.
左から順番に見ているのは, 左からやっていく場合に使いたいからである. 蟻本のような実装だと, tlは左から右へ移動するが, trは右から左に移動する. これでは操作を左からやっていきたいときに困る.
while文をfor文にしたのは, while文だとちょっと遅いからである(定数倍高速化). while文で実装したときは1400msだったが, for文で実装したら858msだった.
第4回では, 先頭固定テクニックとぼくが言う, 主に定数個入力数え上げで平等性が高い状況のときに使えるDPについて書きます.
例題4-1 個の区別する玉を個の区別しない箱に入れる方法の数はいくつですか. ただし, 各箱には必ず個以上の玉を入れるものとします.
つまり, 第2種Stirling数を求めてください.
ただし, 答えはで割った余りで出力してください.時間制限:2秒、メモリ制限:1024 MB
制約
入力
N K
を個の区別する玉を個の区別しない箱に各箱1個以上入れる方法とします.
次ののDPが知られています.
mod = 998244353 n,k = map(int,input().split()) dp = [[0] * (k+1) for j in range(n+1)] dp[0][0] = 1 for i in range(n): for j in range(k): dp[i+1][j+1] = (j+1) * dp[i][j+1] + dp[i][j] dp[i+1][j+1] %= mod print(dp[n][k])
今回は, 先頭固定テクニックで解いてみます. について考えます. ある1つの箱について考えます. その箱は個の玉を含んでいるものとします. このとき, ある玉の組み合わせを固定すると, その組み合わせを含むものは, 通りとなります.
それはそうなのですが, 実際にすべての組み合わせについてこれを足し合わせると(つまりを掛けると), 次の画像のように同じ結果になるものがダブルカウントしてしまうことがあります.
ダブルカウントしない方法として, 「今見ている配列のうち, 先頭を固定する」ということが考えられます. 先頭を固定すれば, 後続は通りですが, 先頭が固定されていることにより遷移先で先頭が証人になっているので, 後々になってダブルカウントすることはありません. これが先頭固定テクニックです.
は, のすべてについての総和となります. をで前計算することにより, このDPは となります.
mod = 998244353 N = 400 fact = [1]*(N+1) factinv = [1]*(N+1) for i in range(2, N+1): fact[i] = fact[i-1] * i % mod factinv[-1] = pow(fact[-1], mod-2, mod) for i in range(N-1, 1, -1): factinv[i] = factinv[i+1] * (i+1) % mod def cmb(a, b): if (a < b) or (b < 0): return 0 return fact[a] * factinv[b] % mod * factinv[a-b] % mod n,k = map(int,input().split()) dp = [[0] * (k+1) for j in range(n+1)] dp[0][0] = 1 for i in range(1,n+1): for j in range(1,k+1): for m in range(1,i+1): dp[i][j] += dp[i-m][j-1] * cmb(i-1, m-1) % mod dp[i][j] %= mod print(dp[n][k])
時間計算量はあまりよくないけれど, 有名な遷移とはまた異なる遷移ができました.
余談ですが, と変形できます. に注目すると, 次の式が得られます.
(ただしは任意の非負整数)
例題4-2 ACM-ICPC Japan Alumni Group Contest 2019 2日目 H
問題文は長いので上のリンクを見てください.時間制限:2秒、メモリ制限:256 MB
制約
長さの順列のスコアの総和を, スコアの2乗の総和をとします.
このとき, スコアの平均値は, 分散は です.
よって, を求めればokです.
順列を対称群の元だと見ると, 「サイクル森の1つの連結成分の数」は, 「巡回置換の長さ」(つまりは閉路からのみなる)と確認することができます.
その閉路に注目します. を求めてみましょう. を, 「先頭に含まれるグループの連結成分の数」とします. このとき, そのようなグループの決め方は個, さらにグループの中の閉路の作り方が個あります. (次対称群の長さの巡回置換は個あるので)
から乗算されるので, 閉路の大きさも掛けて, がに加算されます.
を考慮して, となります.
このように, 先頭固定テクニックは対称群(置換)と相性が良いです.
同様に です.
実はこれでコードができました. しかし間に合わないので工夫をします.
式変形をすると,
で, 順序を変えると
同様に
です.
を持つ変数を管理すれば, この5つは, を1つ増やすごとにで更新できます. よって累積和的に各をで更新でき, 全体でで解けます.
mod = 10**9 + 7 N = 100256 fact = [1]*(N+1) factinv = [1]*(N+1) for i in range(2, N+1): fact[i] = fact[i-1] * i % mod factinv[-1] = pow(fact[-1], mod-2, mod) for i in range(N-1, 1, -1): factinv[i] = factinv[i+1] * (i+1) % mod def cmb(a, b): if (a < b) or (b < 0): return 0 return fact[a] * factinv[b] % mod * factinv[a-b] % mod n = int(input()) a0 = 1 a1 = 0 b0 = 1 b1 = 0 b2 = 0 t = 0 s = 0 for i in range(1, n+1): t = fact[i-1] * (i*a0 - a1) % mod s = fact[i-1] * (i*i*b0 - 2*i*b1 + b2) % mod a0 = (a0 + t*factinv[i]) % mod a1 = (a1 + t*i*factinv[i]) % mod b0 = (b0 + s*factinv[i]) % mod b1 = (b1 + s*i*factinv[i]) % mod b2 = (b2 + s*i*i*factinv[i]) % mod a = t*factinv[n]%mod print((s - 2*t*a + a*a*fact[n])*factinv[n]%mod)
ぼくがABC255のA~F (+コンテスト後にEx)を解くまでの思考過程をまとめてみました.
A : 1分39秒
B : 5分04秒
C : 10分29秒 (1ペナ)
D : 13分18秒
E : 38分26秒
F : 64分47秒
で, 163位でした
本当に素の思考過程なので, 自分流の言葉とかふんわりとした気持ちを使ってます
あと, ネタバレを多く含むので展開式になってます