agc022F - Checkers 题解

每次做这种 atc 风格 DP 都是生不如死……


先扯一些关系不大的东西。

我们知道 \(n\) 个元素合并 \(n-1\) 次可以用一棵 \(2n-1\) 个点的二叉树表示。每次合并都新建节点,并连向两个集合的树的根,得到一棵新树。这样可以描述每次合并的两个集合的无序对的集合,但并不能知道合并的顺序(也不会有题目需要知道顺序),但起码需要符合这棵树的拓扑序。如果 A 合并到 B、B 合并到 A 不同(可以理解为 PK 的胜负,负者淘汰),那么此时这种方法并不能记录每次的输赢,但只需要把胜者放在左儿子、负者放在右儿子即可。

但还有另一种我以前不知道的描述合并关系的方法。这种方法重在描述每次合并的胜负,每次合并设 A 胜 B 负,则令 B 目前的胜者直接指向 A 目前的胜者。这样也可以得到一棵(内向)树,只不过恰有 \(n\) 个节点,且是多叉树(相当于把二叉树的左儿子缩上去)。但普通情况并不能描述每次合并的两个集合的无序对的集合,然而依然可以稍作改进做到。对于某个节点 \(x\),记其儿子序列为 \(A_{1\sim m}\),则必然恰好存在一个排列 \(p\),使得 \(A_{p_i}\) 参与的合并是与 \(\{x\}\cup\bigcup\limits_{j=1}^{i-1}A_{p_j}\) 合并的,那就将儿子序列按照这个排列排序即可。这种方法依然不能知道合并顺序,但可以肯定合并是按照树的拓扑序进行的。所以这和二叉树方法是等价的。

再讲一个大冤种:如果每次是「将 \(x,y\) 所在集合合并」,那么用无向边连接 \(x,y\),会形成一棵无根树。每种断边 / 加边顺序就对应一种合并过程(当然不是双射)。


开始讲这个题。可以认为一开始是 \(n\) 个向量 \(\pmb a_i\),使得 \(a_{i,j}=[i=j]\)。每次 \(\pmb u,\pmb v\) PK 是合并为 \(2\pmb u-\pmb v\)\(2\pmb v-\pmb u\)。注意到,这里涉及胜负,而多叉树描述法擅长描述这个。我之前只知道二叉树描述法,推了半天毫无头绪/yun

考虑其多叉树。每次胜者的集合里所有 \(i\) 对最终向量的第 \(i\) 分量贡献了 \(-1\) 的系数,负者贡献 \(2\)。设第 \(i\) 分量为 \((-1)^{c_i}2^{d_i}\),尝试将 \(c_i,d_i\) 在树上表示出来。\(d_i\) 显然就是 \(i\) 的深度。而 \(c_i\) 比较复杂,好在我们只关心其奇偶性。

\(i\) 往上爬的过程中,每爬一次看它在当前节点的儿子序列中排第 \(\pi\) 个,则说明所在集合胜了 \(\pi-1\) 次(这里排列 \(p\) 与之前的描述是相反的/cy),对 \(c_i\)\(\pi-1\) 的贡献。而它胜儿子们还有额外 \(s_i\) 的贡献,其中 \(s_i\)\(i\) 的儿子树。这样的表达太难受了,考虑写一个用父亲的 \(c\) 值表达的递推式。容易写出 \(c_i=c_{fa_x}-s_{fa_x}+(\pi_f-1)+s_x\),其中 \(\pi_f\) 往上爬一次的 \(\pi\)。如果再将 \(p\) 的含义 reverse 一次,表达式则简单很多,为 \(c_i=c_{fa_x}+\pi_f+s_x\)(当然是模 \(2\) 意义下的)。

又注意到:设最终能得到某一个向量 \(\pmb v\),其元素的可重集为 \(S\),则任意可重集为 \(S\) 的向量都能达到(只要置换一下元素的地位即可)。这意味着我们可以从可重集的角度出发去计数,但要乘上 \(\dfrac{n!}{\prod\limits cnt_x!}\),这要求我们的 DP 是以一种一种的 \((c\bmod2,d)\) 为阶段的。

下面正式开始 DP。设 \(dp_{i,d}\) 为有 \(i\) 个节点、至多 \(d\) 层的所有多叉树的所有可能的可重集的可重集排列数之和。由于以层为阶段,每层的 \(d\) 相等,只有两种可能的 \(c\bmod 2\),极易做到转移时立刻决定 \(\dfrac{1}{cnt_x!}\) 并以其为系数。再仔细想想,其实根本不需要记录 \(d\),直接当作在考虑目前这棵树的最后一层即可,毕竟各层无本质区别(第一层除外,必须恰有一个点,所以要特判 \(i=1\))。

考虑枚举最后一层的 \(c=0\) 的个数 \(x\),以及 \(c=1\) 的个数 \(y\)。考察递推式 \(c_i=c_{fa_x}+\pi_f+s_x\),最后一层 \(s\) 值为 \(0\),所以仅考虑 \(c_{fa_x}\)\(\pi_f\)。那么对于倒数第二层的某个节点 \(u\),如果 \(s_u\) 是偶数,则它的儿子们的 \(c_{fa_x}\) 固定,\(\pi_f\) 却有一半是 \(0\) 一半是 \(1\),所以儿子们的 \(c\) 值一定是一半 \(0\) 一半 \(1\)。一直这样下去的话必有 \(x=y\),需要 \(s_u\) 为奇数来调节。类似推理,可以知道这样 \(u\) 的儿子们中 \(c=\lnot c_{fa_x}\) 的会比 \(c=c_{fa_x}\) 的多一个。

于是一种方案就是倒数第二层有 \(|x-y|\)\(s_u\) 为奇数的 \(u\),且这些 \(u\)\(c\) 值都要等于 \([x>y]\),偶数点无所谓,但要保证奇偶总共至少有一个节点。只要是存在对应多叉树满足这个条件的向量元素可重集,便可将 \(x\)\(0\)\(y\)\(1\) 接在下面,于是直接转移到对应状态即可。但是有那么一种可能,就是倒数第二层 \(s_u\) 为奇数的 \(u\) 的个数还可以是大于 \(|x-y|\) 且与之奇偶性相同的数 \(m\),并且 \(c=[x>y]\) 的比 \(c=\lnot[x>y]\) 的多 \(|x-y|\) 个。把这些情况的 DP 值都加在一起吗?对不起,可能有重复。一种理想情况是,存在某一种状态的所有可能可重集完全包含其它状态,那就可以只算这个状态。幸运的是,可以证明 \(|x-y|\) 个奇数的状态就是完全包含其它的。因为若超过 \(|x-y|\),则必存在两个奇数点的 \(c\) 值不同,则将其中一个的儿子给另一个,该层 \(c\) 值可重集不变,且奇数点减少了两个。

好的,现在将 DP 定义扩展至 \(dp_{i,j,k=0/1}\) 表示 \(i\) 个数,最后一层 \(\sum[2\nmid s_u]=j\),且这 \(j\) 个的 \(c\) 值必须都等于 \(k\)。重新考虑转移,可以发现相比于之前多了 \(s_u\) 的影响,之前都是 \(0\) 的。那么设 \(c'_u=c_u-s_u\),即可跟之前差不多。枚举 \(x,y\) 分别表示 \(s_u\) 为偶数的点中 \(c'=0/1\) 的数量,则根据 \(j,k\) 容易算出 \(x',y'\) 分别表示所有点中 \(c'=0/1\) 的数量,以及 \(x'',y''\) 分别表示所有点中 \(c=0/1\) 的数量。那么贡献为 \(\dfrac{1}{x''!y''!}dp_{i-j-x-y,|x'-y'|,[x'>y']}\)

这样时间复杂度是 \(\mathrm O\!\left(n^4\right)\),可以过 agc 原题,但过不了模拟赛(魔鬼笑)。

先放个四方代码
constexpr int N = 510;

int n;
int iv[N], fc[N], ifc[N];
int dp[N][N][2];

void mian() {
	n = read(), P = read();
	iv[1] = 1; REP(i, 2, n) iv[i] = (ll)iv[P % i] * (P - P / i) % P;
	fc[0] = ifc[0] = 1; REP(i, 1, n) fc[i] = (ll)fc[i - 1] * i % P, ifc[i] = (ll)ifc[i - 1] * iv[i] % P;
	dp[1][0][0] = dp[1][0][1] = dp[1][1][1] = 1;
	REP(i, 2, n) REP(j, 0, i - 1) REP(k, 0, 1) {
		REP(x, 0, i - 1 - j) REP(y, 0, i - 1 - j - x) if(j || x || y) {
			int x0 = x + (k == 1) * j, y0 = y + (k == 0) * j;
			int x00 = x + (k == 0) * j, y00 = y + (k == 1) * j;
			int ad = x0 < y0 ? dp[i - j - x - y][y0 - x0][0] : dp[i - j - x - y][x0 - y0][1];
			addto(dp[i][j][k], (ll)ifc[x00] * ifc[y00] % P * ad % P);
		}
	}
	int ans = dp[n][0][0];
	ans = (ll)fc[n] * ans % P;
	prt(ans), pc('\n');
}

考虑优化到 \(\mathrm O\!\left(n^3\right)\)

观察代码,枚举 \(i,j,k,x,y\),考虑分成四元组 \((i,j,k,x)\)\(y\) 来查看。

总贡献为 \(\sum\limits_{x}\dfrac{1}{(x+(1-k)j)!}\sum\limits_y\dfrac{1}{(y+kj)!}f(i,j,k,x,y)\)(这里 \(i,j,k\) 虽然不在求和枚举里,但也是被枚举的变量)。如果能 \(\mathrm O(1)\) 知道后面这个 \(\sum\) 的值,便达到了三方。注意到该 \(\sum\) 仅与 j || x\(i-j-x\)x + (k ? j : -j)\(kj\) 有关,其中 j || x 是 bool 值其复杂度可以忽略。这样还是涉及三个 \(\mathrm O(n)\) 的量,有 \(\mathrm O\!\left(n^3\right)\)\(\sum\) 的值(flag),计算每种的话加上枚举 \(y\) 还是四方的复杂度。

但是注意到最后一项 \(kj\),如果 \(k=0\) 就不需要记录了,那么就只有 \(\mathrm O\!\left(n^2\right)\) 种,对每种暴力计算复杂度就是三方了。那 \(k=1\) 呢?很简单,交换 \(x,y\) 地位即可。

略微卡常,用了 18 次乘法取一次模的优化才过/qd

code
constexpr int N = 510;

int n;
int iv[N], fc[N], ifc[N];
int dp[N][N][2];
ull f[N][3 * N][2][2];
int vis[N][3 * N][2];

void mian() {
	n = read();
	iv[1] = 1; REP(i, 2, n) iv[i] = (ll)iv[P % i] * (P - P / i) % P;
	fc[0] = ifc[0] = 1; REP(i, 1, n) fc[i] = (ll)fc[i - 1] * i % P, ifc[i] = (ll)ifc[i - 1] * iv[i] % P;
	dp[1][0][0] = dp[1][0][1] = dp[1][1][1] = 1;
	memset(f, -1, sizeof(f));
	REP(i, 2, n) REP(j, 0, i - 1) REP(k, 0, 1) { // 这优化,多是一件美逝啊…… 
		if(k == 0) {
			REP(x, 0, i - 1 - j) {
				ull &F = f[i - j - x][x + (k ? j : -j) + n][k][j || x];
				if(!~F) {
					F = 0; int c = 0;
					REP(y, 0, i - 1 - j - x) if(j || x || y) {
						int x0 = x + (k == 1) * j, y0 = y + (k == 0) * j;
						int y00 = y + (k == 1) * j;
						int ad = x0 < y0 ? dp[i - j - x - y][y0 - x0][0] : dp[i - j - x - y][x0 - y0][1];
						F += (ll)ifc[y00] * ad;
						if(++c == 18) F %= P, c = 0;
					} F %= P;
				}
				int x00 = x + (k == 0) * j;
				addto(dp[i][j][k], ifc[x00] * F % P);
			}
		} else {
			REP(y, 0, i - 1 - j) {
				ull &F = f[i - j - y][y + (!k ? j : -j) + n][k][j || y];
				if(!~F) {
					F = 0; int c = 0;
					REP(x, 0, i - 1 - j - y) if(j || x || y) {
						int x0 = x + (k == 1) * j, y0 = y + (k == 0) * j;
						int x00 = x + (k == 0) * j;
						int ad = x0 < y0 ? dp[i - j - x - y][y0 - x0][0] : dp[i - j - x - y][x0 - y0][1];
						F += (ll)ifc[x00] * ad;
						if(++c == 18) F %= P, c = 0;
					} F %= P;
				}
				int y00 = y + (k == 1) * j;
				addto(dp[i][j][k], ifc[y00] * F % P);
			}
		}
	}
	int ans = dp[n][0][0];
	ans = (ll)fc[n] * ans % P;
	prt(ans), pc('\n');
}
posted @ 2022-04-05 18:12  ycx060617  阅读(122)  评论(0编辑  收藏  举报