Checkers

\(x = 10^{100}\),一维数轴上有\(n\)个棋子,第\(i\)个棋子的坐标为\(x_i\)。每次操作选择两个棋子\(i,j\),之后\(i\)跳到关于\(j\)的对称点上,删掉\(j\)

\(n − 1\)次操作后,剩下的棋子可能的坐标个数。答案对\(10^9 + 7\)取模。

\(n ≤ 50\)

题解

陆宏《动态规划》。

注意到棋子坐标很大,所以对于一个坐标,只需要关心\(x_i\)的系数。

两个方案不同当且仅当存在一个\(x_i\)的使得它在两个方案里的系数不同。

两个棋子合并,其实就是将一个棋子坐标\(× 2\),另一个棋子坐标\(× (−1)\),然后加起来。

那么,\(x_i\)的系数一定是\((−1)^p × 2^k\)的形式,如果确定了系数中\(2^k\)\(−2^k\)的个数即可统计答案。显然答案是可重排列组合数。

当两个棋子合并时,新建一个点,把这两个点向新建的点分别连一条\(2\)的边和一条\(−1\)的边。最后会连出一棵树,每个叶子节点的权值为它到根路径上的边权值之积。这些权值便是一组合法的系数。

由于不同的树对应的系数可能相同,所以需要换一个角度思考。

例如\((-1)\times 2=2\times (-1)\)

不妨从根开始考虑,每个节点可以向下伸出一条\(2\)的边和一条\(-1\)的边。每次可以把一个\((−1)^p × 2^k\)分裂成一个\((−1)^p × 2^{k+1}\)和一个\((−1)^{p+1} × 2^k\),直到分裂出\(n\)个叶节点。

那么从\(2\)的幂次小到大考虑,一开始有一个\(2^0\),记\(F[i][x][y]\)表示目前已经有\(i\)个叶节点,对于当前幂次,有\(x\)个系数是负的,\(y\)个系数是正的。

因为我们只需要\(2^k\)\(-2^k\)的数量,所以我们不需要知道\(k\)具体是多少。

枚举分裂\(a\)个负的,分裂\(b\)个正的乘两个组合数转移到\(F[i + x + y][a][b]\)

注意到分裂的变化形如\(2^k\rightarrow (2^{k+1},-2^k),-2^k\rightarrow (-2^{k+1},2^k)\),是有一定规律的。具体而言:

  • 每生成一个\(2^{k+1}\),就会用掉一个\(2^k\),回收一个\(-2^k\)

  • 每生成一个\(-2^{k+1}\),就会用掉一个\(-2^k\),回收一个\(2^k\)

那么对于当前幂次,\(x\)将会变化成\(x-a+b\)\(y\)将会变化成\(y+a-b\)。不难发现只要\(x-a+b\geq 0,y+a-b\geq 0\),就一定存在一种合法的分裂方案。

综上有\(F[i][x][y]\leftarrow F[i+x+y][a][b]\div (x-a+b)!\div (y+a-b)!\)

最终答案为\(F[0][1][0]\times n!\)

时间复杂度\(O(n^5)\)

CO int N=51;
int fac[N],ifac[N];
int F[N][N][N];

int main(){
	int n=read<int>();
	fac[0]=1;
	for(int i=1;i<=n;++i) fac[i]=mul(fac[i-1],i);
	ifac[n]=fpow(fac[n],mod-2);
	for(int i=n-1;i>=0;--i) ifac[i]=mul(ifac[i+1],i+1);
	F[n][0][0]=1;
	for(int i=n-1;i>=0;--i)
		for(int x=0;x<=n-i;++x)for(int y=0;y<=n-i-x;++y){
			for(int a=0;a<=n-i-x-y;++a)for(int b=0;b<=n-i-x-y-a;++b)
				if(x-a+b>=0 and y+a-b>=0)
					F[i][x][y]=add(F[i][x][y],mul(F[i+x+y][a][b],mul(ifac[x-a+b],ifac[y+a-b])));
		}
	int ans=mul(fac[n],F[0][1][0]);
	printf("%d\n",ans);
	return 0;
}

注意到,当\(x − y\)相同时,\(F[i][x][y]\)的值也相同,所以只需要考虑转移到\(F[i][x][0]\)或者\(F[i][0][y]\)的情况即可。

我打了个表没发现对角线相同……实际上上面那份代码的DP写反了。

如果用\(F[i][x][y]\div (x-a+b)!\div (y+a-b)!\rightarrow F[i+x+y][a][b]\)这样的顺序来转移的话,转移系数就只与\(a-b\)的值有关了。

复杂度优化至\(O(n^4)\)

CO int N=51,O=50;
int fac[N],ifac[N];
int F[N][2*N];

int main(){
	int n=read<int>();
	fac[0]=1;
	for(int i=1;i<=n;++i) fac[i]=mul(fac[i-1],i);
	ifac[n]=fpow(fac[n],mod-2);
	for(int i=n-1;i>=0;--i) ifac[i]=mul(ifac[i+1],i+1);
	F[0][O+1]=1;
	for(int i=0;i<=n-1;++i){
		if(i==0){
			int x=1,y=0;
			for(int d=-y;d<=x;++d)
				F[i+x+y][O+d]=add(F[i+x+y][O+d],mul(F[i][O+x-y],mul(ifac[x-d],ifac[y+d])));
			continue;
		}
		for(int x=0;x<=n-i;++x)for(int y=0;y<=n-i-x;++y)if(x+y>0)
			for(int d=-y;d<=x;++d)
				F[i+x+y][O+d]=add(F[i+x+y][O+d],mul(F[i][O+x-y],mul(ifac[x-d],ifac[y+d])));
	}
	int ans=mul(fac[n],F[n][O]);
	printf("%d\n",ans);
	return 0;
}