[ 计数dp] [dp优化] AT_agc022_f [AGC022F] Checkers
posted on 2025-06-18 10:10:08 | under | source
题意:记 \(w=10^{100}\),有 \(n\) 个点,第 \(i\) 个在 \((w^i,0)\)。你每次选择两个不同的两个点 \(x,y\),将 \(x\) 移到关于 \(y\) 的对称点并移除 \(y\)。进行 \(n-1\) 次后只剩一个点,求这个点坐标有几种可能?\(n\le 50\)。
先做一步转化,将点的横坐标视作 \(w\) 进制数,由于 \(w>2^{50}\) 所以不可能发生进位。所以我们完全可以拆位考虑。
建模,每次操作 \(x,y\) 就让 \(y\to x\),那么得到一棵内向树。点 \(i\) 对答案产生的贡献即为 \(2^{d_i}\times opt_i\)。\(d_i\) 即为 \(i\) 到根的边数也就是深度,很好理解。\(opt_i=\pm 1\),这是因为操作会导致变号。
\(opt_i\) 不好直接描述,但是你发现它的差分是好处理的。具体来说,考虑一个点 \(u\),那么它先会被 \(son_u\) 个孩子取反 \(son_u\) 次;然后在记它是父亲 \(fa\) 第 \(rk_u\) 个操作的,\(fa\) 在操作它时符号为 \((-1)^{rk_u}\),此后 \(u,fa\) 的符号同时改变。所以得到结论:\(opt_u=opt_{fa}\) 当且仅当 \(son_u\) 和 \(rk_u\) 奇偶性相同。
大力计数!我们必然是一层一层添加点的,因为若每一层点的分布不同则最终方案必然不同。然后我们修改得到 \(opt\) 的方式以便计数:首先直接继承父亲的 \(opt\),然后让它取反 \(rk_u\) 次,最后我们确定 \(son_u\) 后让它再取反 \(son_u\) 次。这是等价的。
根据这个框架设计状态,记 \(f_{i,j}\) 为已经填了 \(i\) 个点,最后一层有 \(j\) 个点满足 \(2\mid son_u\)(就叫它们特殊点)。这个状态有点莫名其妙,没事看下去就好了。
接下来考虑转移。你发现很容易算重,不妨归纳地认为 \(f_i\) 所有局面(就是到 \(f_i\) 的所有转移路径得到的结果)不重不漏。也就是对重复局面选取一个代表元,接下来在转移的过程中要着重考虑代表元的选取使其能覆盖所有转移情况。
观察到一个重要性质:一个局面,若满足某一层的特殊点的 \(opt\) 不相同,则这个局面可以通过调整特殊点的选取而得到相同的 \(opt\) 分布。证明的话考虑同层两个 \(opt\) 不同的特殊点 \(x,y\),那么将 \(x\) 最后一个儿子送给 \(y\),可以验证 \(opt\) 分布不变,并且现在 \(x,y\) 的都不是特殊点。
这也就意味着我们只需要把同层特殊点 \(opt\) 相同的状态扔进 \(f\) 里。
终于可以转移了,对于 \(f_{i,j}\) 里的一个局面,记 \(o\) 为它最后一层特殊点的符号,枚举 \(x,y\) 为下一层 \(opt\) 与 \(o\) 相同点有 \(x\) 个、不同的有 \(y\) 个。然后乘个组合数 \({{n-i}\choose {x+y}}{{x+y}\choose x}\) 表示选取当前层的点的编号、钦定点的 \(opt\) 是否和 \(o\) 相同。
我们先考虑“继承父亲、取反 \(rk\) 次”这一步,那么会有 \(j\) 个点分别连向每个特殊点,其符号为 \(o\);接下来只能两个两个填,所以它们正负号数量相同,这和父亲 \(opt\) 具体是啥没有关系。也就是说有 \(\frac {x+y+j}2\) 个符号为 \(o\) 的。上述讨论只考虑了数量是因为和点的编号无关,所以可以随意分配符号只要数量正确。
最后再考虑“确定 \(son_u\)”也就是分配特殊点。先考虑 \(\frac {x+y+j}2 \le x\),你发现只能将所有 \(o\) 分配给被钦定为 \(o\) 的点,然后剩下 \(x-\frac {x+y+j}2\) 个没被分配的点让他变成特殊点,其它的点不能是特殊点。原因是若给钦定为 \(-o\) 的点分配一个 \(o\),那么必然存在一个被钦定为 \(o\) 的点没被分配到,必须把它们设为特殊点,此时就违反了上面的“重要性质”。\(\frac {x+y+j}2 >x\) 同理。总的来说就是恰好设 \(|x-\frac {x+y+j}2|\) 个特殊点,显然具体分配给谁都是等价的。
通过这种方式,使得局面不重不漏。最终转移为:
主要限制为:\(2\mid x+y+j\)、\(x+y>0\)、\(x+y\ge j\)。
暴力 \(O(n^4)\),足以通过。后面的优化部分不过是代数推导保平安,但也挺新奇的。
注意到这个组合数可拆为 \(\frac {(n-i)!}{(n-i-x-y)!x!y!}\),令 \(f_{i,j}\) 乘上 \((n-i)!\) 就得到了优美结构:
启发我们将 \(x,y\) 分开转移。设中间状态 \(g_{i,j}\),先转移 \(f_{i,j}\to g_{i+x,x-j}\),然后再 \(g_{i,j}\to f_{i+y,|\frac {j-y}2|}\)。奇偶性限制在 \(y\) 的转移时易判定,\(x+y\ge j\) 等价于 \(y\ge j-x\) 也易判定。\(x+y>0\) 就只转移 \(x>0\) 的,然后再直接转移补上 \(x=0\) 的。
变成了 \(O(n^3)\)。具体细节可以看 Alex_Wei 大神的博客,写的非常详细。
闲话
上次看这题还是上次 noip 前几天,然后对着若干不太严谨的题解一头雾水后放弃了。现在终于看懂了,故作一篇题解。这可能是初中写的最后一篇题解,唉。
今天是 22 号,广东中考就只剩一周多一点了,希望这篇题解能给我涨涨 RP。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ADD(a, b) a = (a + b) % mod
#define abs(a) ((a) < 0 ? -(a) : (a))
const int N = 55, mod = 1e9 + 7;
int n, jc[N], jcinv[N], f[N][N], g[N][N << 1];
inline int qstp(int a, int k) {int res = 1; for(; k; a = a * a % mod, k >>= 1) if(k & 1) res = res * a % mod; return res;}
signed main(){
jc[0] = jcinv[0] = 1;
for(int i = 1; i < N; ++i) jc[i] = jc[i - 1] * i % mod, jcinv[i] = qstp(jc[i], mod - 2);
cin >> n;
f[1][0] = f[1][1] = jc[n];
for(int i = 1; i <= n; ++i){
for(int j = -n; j <= n; ++j)
for(int y = max(0ll, -j); y <= n - i; ++y)
if((j + y) % 2 == 0) ADD(f[i + y][abs(j - y) / 2], g[i][j + n] * jcinv[y] % mod);
for(int j = 0; j <= i; ++j){
for(int x = 1; x <= n - i; ++x) ADD(g[i + x][x - j + n], f[i][j] * jcinv[x] % mod);
for(int y = max(j, 1ll); y <= n - i; ++y)
if((y + j) % 2 == 0) ADD(f[i + y][abs(j + y) / 2], f[i][j] * jcinv[y] % mod);
}
}
cout << f[n][0];
return 0;
}

浙公网安备 33010602011771号