首先我们对n个数进行一个分组,把乘积为平方数的数分在一组(可以证明:同一组内的数两两乘积为平方数,不同组的两个数乘积一定不是平方数),记作一共分了nn组,则同组的数只要不相邻即可满足题意。即原问题转化为 n个物品,分成了nn组,要求同组物品不能相邻,问共有几种排列方案。我们dp解决这个问题,f[i][j]表示前i组,有j个相邻的同组的方案数。
我们现在考虑插入第i组,第i组的大小为size[i],前面已经填了tot个数,我们把第i组分成k部分插入tot+1个空档(已经填的数两两之间和开头结尾称为空档),插入的k个空档中有D个空档两边为同组的数(则插入后消去了这D个相邻的同组),则有
f[i][j-D+size[i]-k]+=f[i-1][j]*T。(原来有j个相邻的同组,消去了D个,这次新添进来size[i]-k个相邻的同组)
T表示这种插入方式的种数,为
Ck−1size−1∗CDj∗Ck−Dtot+1−j
。(乘法原理:把size个数分成k部分的种数* 在j个可以消去的相邻同组中选D个的种数 * 在tot+1-j个空当中选k-D个空档的种数(另外D个必须填在所选的D个相邻同组之间))
初值f[0][0]=1,答案就是f[nn][0]。
这是所有的组的排列方案,但是我们还没有考虑具体的每组的数的顺序,所以答案再乘上所有的组的排列可能即每组size的阶乘。
这种题的原型就是 n个物品,分成了nn组,要求同组物品不能相邻,问共有几种排列方案。可以变换成多种题,套路是一样的。
#include <bits/stdc++.h>
#define N 305
#define ll long long
int const mod=1e9+7;
int n,a[N],size[N],nn=0,f[N][N],C[N][N];//f[i][j]表示前i组,有j个相邻的同组
ll fact[N];
bool vis[N];
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x*f;
}
inline bool check(ll x){
ll xx=sqrt(x);
if(xx*xx==x) return 1;
else return 0;
}
inline void calc(){//预处理组合数和阶乘
C[0][0]=1;
for(int i=1;i<=n;++i){
C[i][0]=1;
for(int j=1;j<=i;++j)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
fact[0]=1;
for(int i=1;i<=n;++i) fact[i]=(ll)fact[i-1]*i%mod;
}
int main(){
// freopen("a.in","r",stdin);
n=read();for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=n;++i){
if(vis[i]) continue;
vis[i]=1;size[++nn]=1;
for(int j=i+1;j<=n;++j)
if(check((ll)a[i]*a[j])) vis[j]=1,size[nn]++;
}
calc();int tot=0;f[0][0]=1;
for(int i=1;i<=nn;++i){
for(int j=0;j<=tot;++j)//dp[i-1][j]
for(int k=1;k<=size[i]&&k<=tot+1;++k)//把第i组分成k部分
for(int D=0;D<=k&&D<=j;++D)//消掉D个连续的同组
f[i][j-D+size[i]-k]=(f[i][j-D+size[i]-k]+(ll)f[i-1][j]*C[size[i]-1][k-1]%mod*C[j][D]%mod*C[tot+1-j][k-D])%mod;
tot+=size[i];
}
ll ans=f[nn][0];
for(int i=1;i<=nn;++i) ans=ans*fact[size[i]]%mod;//乘上排列总数
printf("%lld\n",ans);
return 0;
}