https://arc087.contest.atcoder.jp/
C
はい。
D
XY独立にできます。
E
本質っぽいgrundyのところまではわかったけどtrie木で敗北しました。
まずstringを2分木に対応させます。そうするといろいろな高さの2分木から頂点を取るゲームになります。
高さがdの2分木のgrundy数はdを2で割り切れる回数+1となるのであとは、trie木で片方しか頂点がない頂点を探せばいいです。
ll L; int div(ll a) { int cnt = 0; while(a % 2 == 0) { cnt++; a /= 2; } return cnt; } struct trie { int G[MAX_N][2]; int A[100]; int M; trie() { init(); } void init() { memset(G, -1, sizeof(G)); M = 1; } void add(const string& str) { int n = sz(str); int v = 0; rep(i, 0, n) { int y = str[i] - '0'; if(G[v][y] == -1) { G[v][y] = M++; } v = G[v][y]; } } void show() { rep(i, 0, 10) { debug(i, G[i][0], G[i][1]); } } void dfs(int v, int d) { // debug(v, d); int cnt = 0; if(G[v][0] != -1) { dfs(G[v][0], d + 1); cnt++; } if(G[v][1] != -1) { dfs(G[v][1], d + 1); cnt++; } if(cnt == 1) { if(L - d >= 1) A[div(L - d)]++; } } bool win() { dfs(0, 0); rep(i, 0, 70) { if(A[i] % 2) return true; } return false; } }; int N; trie T; string str[MAX_N]; void solve() { cin >> N >> L; rep(i, 0, N) { cin >> str[i]; T.add(str[i]); } if(T.win()) { cout << "Alice\n"; } else cout << "Bob\n"; }
F
これは初手がわからないと詰むパターンですね…
Σd(k,pk)=Σd(root,k)+d(root,pk)-2d(root, lca(k, pk))ですが、Σd(root,k)+d(root,pk)は一定なので、d(root, lca(k, pk))を最小化すればいいです。
さらにd(root, lca(k, pk)) = 0 <=> rootは重心が成立します。ここが難しいです。まあ木上での距離の和=>重心つかうは定石なんだけども。
証明はそんな大変じゃないです。少し試せばわかります。
重心が2つの時は単純で答えは((N/2)!)^2です。
重心が1つの時、lca(k,pk)!=rootとなるようなパーミュテーションを全体から引きたいので包除原理をします。
重心から生えてる木をT1...,TKとすれば
x1<=T1, ... xK<=TK、Σxi=xとすれば、求める値は
Σ(x=0→N-1)(-1)^x(N-x)! {Σ(x1,x2...xK)Π(k=1→K)C(Tk, xk)^2 xk!}となります。
この{}内をdp[i][x]:=Σ(k=0...i)xi = xの時のΣ(x1,x2...xi)Π(k=1→i)C(Tk, xk)^2 xk! と定義して求めます。
一見O(N^3)に見えますが、よくある木dpのようにO(N^2)となって間に合います。
int N; vector<int> cen; vector<int> G[MAX_N]; ll dp[2][MAX_N]; int search_centroid(int v, int p) { int s = 1, m = 0; rep(i, 0, sz(G[v])) { int n = G[v][i]; if(p == n) continue; int t = search_centroid(n, v); s += t; MAX(m, t); } MAX(m, N - s); if(m <= N / 2) { cen.pb(v); } return s; } void solve() { cin >> N; C_init(N); rep(i, 0, N - 1) { int a, b; cin >> a >> b; a--; b--; G[b].pb(a); G[a].pb(b); } search_centroid(0, -1); if(sz(cen) == 2) { cout << fac[N / 2] * fac[N / 2] % mod << "\n"; } else if(sz(cen) == 1) { int c = cen[0]; vector<int> vec; rep(i, 0, sz(G[c])) { int n = G[c][i]; vec.pb(search_centroid(n, c)); } int K = sz(vec); dp[0][0] = 1; int s = 0; int now = 0, next = 1; rep(i, 0, K) { memset(dp[next], 0, sizeof(dp[next])); rep(j, 0, s + 1) { if(!dp[now][j]) continue; int t = vec[i]; rep(k, 0, t + 1) { ADD(dp[next][j + k], dp[now][j] * C(t, k) % mod * C(t, k) % mod * fac[k] % mod); } } s += vec[i]; swap(now, next); } ll ans = 0; rep(i, 0, N) { if(i % 2) ADD(ans, mod - fac[N - i] * dp[now][i] % mod); else ADD(ans, fac[N - i] * dp[now][i] % mod); } cout << ans << "\n"; } }
sugimさんみたいに数式を効率良くつかって解けるようになりたいね。