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;
}