题目大意:
给你
n(n<=300)
n
(
n
<=
300
)
个数,问有多少个排列满足任意相邻两个数的积不为完全平方数。(相同数字交换位置算不同的排列,可以知道,总排列数一定有
n!
n
!
个)。
分析:
如果
a∗b
a
∗
b
为完全平方数,
a∗c
a
∗
c
为完全平方数,那么
b∗c
b
∗
c
也为完全平方数。
证明:对于任意一个数
x
x
,质因数分解后如果每一个质数的指数都是偶数(也算偶数),那么这个数就是完全平方数。根据每一个质数指数的奇偶性,我们可以写出一个二进制数,记为
bitx
b
i
t
x
。而两数相乘,其质数的指数相加,对于奇偶性就是异或。因为,
bita xor bitb=0
b
i
t
a
x
o
r
b
i
t
b
=
0
,
bita xor bitc=0
b
i
t
a
x
o
r
b
i
t
c
=
0
,所以
bitb=bitc
b
i
t
b
=
b
i
t
c
,即
bitb xor bitc=0
b
i
t
b
x
o
r
b
i
t
c
=
0
。因此,
b∗c
b
∗
c
也是完全平方数。
所以我们可以把两两可以组成完全平方数的数分成
cnt
c
n
t
组,每组大小为
numi
n
u
m
i
。
我们设
f[i][j]
f
[
i
]
[
j
]
为前
i
i
组,有个冲突点(即相邻两个数积为完全平方数)的方案数。因为可以随意调换,所以第
i
i
组有个方案。我们再枚举
k
k
,表示把第个组分成
k
k
块,即放个挡板,方案数为
(k−1numi−1)
(
k
−
1
n
u
m
i
−
1
)
,这样会产生
numi−k
n
u
m
i
−
k
个不合法位置。
设
sum
s
u
m
为前
i−1
i
−
1
个组数的个数,这
k
k
个块可以放入个位置上,而其中
j
j
个位置是不合法位置,枚举有个不非法位置中间插入了当前组的块,这样就会减少
l
l
个不合法位置,也使用了个块。在
j
j
个位置中选择个位置,方案数为
(lj)
(
l
j
)
。在剩下的
sum+1−j
s
u
m
+
1
−
j
个位置中选择剩下的
k−l
k
−
l
个块,方案数为
(k−lsum+1−j)
(
k
−
l
s
u
m
+
1
−
j
)
。
所以,总转移方程式为:
因为 ∑cnti=1numi=n ∑ i = 1 c n t n u m i = n ,所以,总复杂度为 O(n3) O ( n 3 ) 。
代码:
#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]);
}