令\(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;
}