C: Remainder Game - AtCoder Grand Contest 022
気づき
操作はkの降順だけ考えれば良い。 vに対して整数k1で操作したあとk2で操作したとする。 k1 <= k2を仮定する。 vをk1で割った余りv'は v' < k1 を満たす。 k1 <= k2より v'をk2で割った余りもv'である。よってk2による操作で値が変わらず、 k2の操作が無意味になる。
二分探索
0から 0b11111...1110 まで考えられる。 コストを2進数表記したとき、各桁は各操作をやったかどうかのフラグに対応する。 二分探索、あるいは辞書順最小を求める要領でこのコストを決める。 最初の桁、50による操作を考える。 50による操作が必要となるのは 49以下の全ての操作をすべて使っても、50の操作を使わなければ 数列{b}を作れない場合だけ。 つまり
0b01111...1110 *
の操作をしたときに数列{b}を作れるかどうか判定すればいい。 ある値a[i]をb[i]に変更できるかを判定するには dp[x][y] := x番目の操作まで使って値yを作れるかどうか のようなDPを各(a[i], b[i])について解けばいい。 49,48,...,1の各操作についても同様に必要かどうかを決められる。
Kを操作に使う最大の整数とすると 実行時間はO(NK3)
int N, A[51], B[51]; bool dp[51][51]; int f(ll S, int id) { MEM(dp, 0); dp[50][A[id]] = 1; for (int i = 50; i >= 1; --i)rep(j, 51)if (dp[i][j]) { if (S >> i & 1) { dp[i - 1][j%i] = 1; } dp[i - 1][j] = 1; } RT dp[0][B[id]]; } int main() { ios::sync_with_stdio(false); cin.tie(0); cout << fixed << setprecision(20); cin >> N; rep(i, N)cin >> A[i]; rep(i, N)cin >> B[i]; ll ans = 0, bb = (1ll << 51) - 1; for (int k = 50; k >= 1; --k) { bb >>= 1; ll S = ans | bb; int ok = 1; rep(i, N) { ok &= f(S, i); if (!ok)break; } if (!ok) ans |= 1ll << k; } if (ans == 2251799813685246ll)ans = -1; cout << ans << endl; }