IX.[AHOI2018初中组]球球的排列

论DP的百种用法之一

因为DP必须有一种全面的状态,但是这道题……似乎排列等等问题都不是DP擅长处理的地方。

首先分析性质。我们发现,这种不能放在一起的关系具有传递性。因为如果\(xy=a^2,xz=b^2\),那么\(yz=\dfrac{(xy)(yz)}{x^2}=\dfrac{a^2b^2}{x^2}=\Big(\dfrac{ab}{x}\Big)^2\)。

具有传递性的话,我们就会发现,所有不能放在一起的位置,构成了多个团(完全图)

我们就想着把每个团里的所有球都染上同一种颜色,则相同颜色的球不能紧贴在一起。

则我们现在将问题转换为:给你\(n\)个染了色的球,相同的球不能放一起,求排列数。

考虑将这些球按照颜色排序,这样便有了一个合理的(可以抽象出状态的)DP顺序。

我们设\(f[i][j][k]\)表示:

当前DP到第\(i\)位,

有两个球放在一起,它们的颜色相同,并且颜色与第\(i\)位的球不同,这样的对共有\(j\)个,

有两个球放在一起,它们的颜色相同,并且颜色与第\(i\)位的球相同,这样的对共有\(k\)个,

的方案数。

因为我们已经排过序,因此颜色相同的球必定紧贴。

1.第 \(i\) 位的球和第 \(i-1\) 位的球颜色不同。

则DP状态的第三维(即\(k\))必为\(0\),因为不存在在它之前并且和它颜色相同的球。我们只需要枚举第二维\(j\)。

1.1.我们将这个球放在两个颜色不同的球之间。

我们枚举一个\(k'\),表示上一个球所代表的颜色中颜色相同且紧贴的对共有\(k'\)个(\(k'\in[0,j]\))。

则有f[i][j][0]+=f[i-1][j-k'][k']*(i-j),因为共有j-k'个相邻且相同且和上一个球的颜色不同的位置,并且共有i-j个可以放球的位置。

1.2.我们将这个球放在两个颜色相同的球之间。

我们仍然枚举一个\(k'\),意义相同。这时,\(k'\in[0,j+1]\)。

则有f[i][j][0]+=f[i-1][j-k'+1][k']*(j+1)。因为放入这个球后就拆开了一对球,因此原来共有 \(j+1\) 对这样的球。

2.第 \(i\) 位的球和第 \(i-1\) 位的球颜色相同。

我们需要枚举剩余两维\(j,k\)。并且,设在第\(i\)位之前,有\(cnt\)个和第\(i\)位相同的位置。

2.1.我们将这个球放在某个和这个球颜色相同的球旁边。

则共有\(2*cnt-(k-1)\)个这样的位置。

因此有f[i][j][k]+=f[i-1][j][k-1]*(2*cnt-(k-1))

2.2.我们将这个球放在两个颜色相同的球之间。

同1.2,有f[i][j][k]+=f[i-1][j+1][k]*(j+1)

2.3.我们将这个球放在两个颜色不同且与这个球颜色不同的球之间。

这次操作没有添加或删除任何对,并且共有\(i-(2*cnt-k)-j\)个位置。

因此有f[i][j][k]+=f[i-1][j][k]*(i-(cnt*2-k)-j)

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
typedef long long ll;
int n,num[310],dsu[310],f[2][310][310];
vector<int>v;
int find(int x){
	return dsu[x]==x?x:dsu[x]=find(dsu[x]);
}
void merge(int x,int y){
	x=find(x),y=find(y);
	if(x==y)return;
	dsu[x]=y;
}
bool che(ll ip){
	ll tmp=sqrt(ip)+0.5;
	return tmp*tmp==ip;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&num[i]),dsu[i]=i;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(che(1ll*num[i]*num[j]))merge(i,j);
	for(int i=1;i<=n;i++)dsu[i]=find(i);
	sort(dsu+1,dsu+n+1);
	f[0][0][0]=1;
	for(int i=1,cnt;i<=n;i++){
		memset(f[i&1],0,sizeof(f[i&1]));
		if(dsu[i]!=dsu[i-1]){
			cnt=0;
			for(int j=0;j<i;j++){
				for(int k=0;k<=j;k++)f[i&1][j][0]=(1ll*f[!(i&1)][j-k][k]*(i-j)+f[i&1][j][0])%mod;//if we put it between two balls of different colours
				for(int k=0;k<=j+1;k++)f[i&1][j][0]=(1ll*f[!(i&1)][j-k+1][k]*(j+1)+f[i&1][j][0])%mod;//if we put it between two balls of the same colours
			}
		}else{
			for(int j=0;j<i;j++){
				for(int k=1;k<=cnt;k++)f[i&1][j][k]=(1ll*f[!(i&1)][j][k-1]*(cnt*2-(k-1))+f[i&1][j][k])%mod;//if we put it next to a ball of the same colour
				for(int k=0;k<=cnt;k++)f[i&1][j][k]=(1ll*f[!(i&1)][j+1][k]*(j+1)+f[i&1][j][k])%mod;//if we put it between two balls of the same colours
				for(int k=0;k<=cnt;k++)f[i&1][j][k]=(1ll*f[!(i&1)][j][k]*(i-(cnt*2-k)-j)+f[i&1][j][k])%mod;//if we put it between two balls of different colours
			}
		}
		cnt++;
	}
	printf("%d\n",f[n&1][0][0]);
	return 0;
}