题目大意:
给你
个数,问有多少个排列满足任意相邻两个数的积不为完全平方数。(相同数字交换位置算不同的排列,可以知道,总排列数一定有
个)。
分析:
如果
为完全平方数,
为完全平方数,那么
也为完全平方数。
证明:对于任意一个数
,质因数分解后如果每一个质数的指数都是偶数(
也算偶数),那么这个数就是完全平方数。根据每一个质数指数的奇偶性,我们可以写出一个二进制数,记为
。而两数相乘,其质数的指数相加,对于奇偶性就是异或。因为,
,
,所以
,即
。因此,
也是完全平方数。
所以我们可以把两两可以组成完全平方数的数分成
组,每组大小为
。
我们设
为前
组,有
个冲突点(即相邻两个数积为完全平方数)的方案数。因为可以随意调换,所以第
组有
个方案。我们再枚举
,表示把第
个组分成
块,即放
个挡板,方案数为
,这样会产生
个不合法位置。
设
为前
个组数的个数,这
个块可以放入
个位置上,而其中
个位置是不合法位置,枚举有
个不非法位置中间插入了当前组的块,这样就会减少
个不合法位置,也使用了
个块。在
个位置中选择
个位置,方案数为
。在剩下的
个位置中选择剩下的
个块,方案数为
。
所以,总转移方程式为:
因为 ,所以,总复杂度为 。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long
const LL maxn=307;
const LL mod=1e9+7;
using namespace std;
LL num[maxn],a[maxn];
LL f[maxn][maxn];
LL n,cnt,x,sum;
LL jc[maxn],njc[maxn];
LL power(LL x,LL y)
{
if (y==1) return x;
LL c=power(x,y/2);
c=(c*c)%mod;
if (y%2) c=(c*x)%mod;
return c;
}
LL C(LL x,LL y)
{
if ((x<0) || (y<0) || (y>x)) return 0;
if ((x==0) || (y==x)) return 1;
return jc[x]*njc[y]%mod*njc[x-y]%mod;
}
int main()
{
scanf("%lld",&n);
for (LL i=1;i<=n;i++)
{
scanf("%lld",&x);
LL flag=0;
for (LL j=1;j<=cnt;j++)
{
LL c=trunc(sqrt(x*a[j]));
if (c*c==x*a[j])
{
flag=1;
num[j]++;
break;
}
}
if (!flag) num[++cnt]=1,a[cnt]=x;
}
jc[0]=jc[1]=1;
njc[0]=njc[1]=1;
for (LL i=2;i<=n;i++)
{
jc[i]=(LL)(jc[i-1]*i)%mod;
njc[i]=power(jc[i],mod-2);
}
f[0][0]=1;
for (LL i=1;i<=cnt;i++)
{
for (LL j=0;j<=sum;j++)
{
for (LL k=1;k<=num[i];k++)
{
for (LL l=0;l<=j;l++)
{
LL d=j-l+num[i]-k;
if ((d>=0) && (d<n))
{
f[i][d]=(f[i][d]+jc[num[i]]*C(num[i]-1,k-1)%mod*C(j,l)%mod*C(sum+1-j,k-l)%mod*f[i-1][j]%mod)%mod;
}
}
}
}
sum+=num[i];
}
printf("%I64d\n",f[cnt][0]);
}