问题描述
给定一个序列 (a(a_ile 10^9)),长度为 (n(nle 300))。
试求有多少 1 到 (n) 的排列 (p_i),满足对于任意的 (2le ile n) 有 (a_{p_{i-1}} imes a_{p_i}) 不为完全平方数,答案对 (10^9+7) 取模。
样例输入
3
1 2 4
样例输出
2
解析
一个数可以拆成一个完全平方数乘上另一个数。不妨将每个数的完全平方数因子去掉,问题就变成了如何使一个序列的相邻两个数不同。
考虑从小到大依次放数。设 (f_{i,j,k}) 表示当前考虑第 (i) 个数,一共有 (j) 个数相邻,其中 (k) 个与当前数相同。记 (cnt) 表示前面有几个数和自己相同。有如下几种情况:
- 当前数放在某个和自己相同的数旁边。容斥一下,有(f_{i,j+1,k+1}=f_{i-1,j,k} imes (2*cnt-k))。
- 放在某两个相邻的数中间,但不跟自己相同。(f_{i,j-1,k}=f_{i-1,j,k} imes (j-k))。
- 放在了其他地方。(f_{i,j,k}=f_{i-1,j,k} imes [i-(2cnt-k)-(j-k)])。
当前数与前面放的数不同时,注意对DP数组进行修改。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long
#define N 302
using namespace std;
const int mod=1000000007;
int n,i,j,k,a[N],f[2][N][N],x,cnt;
int read()
{
char c=getchar();
int w=0;
while(c<'0'||c>'9') c=getchar();
while(c<='9'&&c>='0'){
w=w*10+c-'0';
c=getchar();
}
return w;
}
signed main()
{
n=read();
for(i=1;i<=n;i++) a[i]=read();
for(i=1;i<=n;i++){
for(j=2;j*j<=a[i];j++){
while(a[i]%(j*j)==0) a[i]/=j*j;
}
}
sort(a+1,a+n+1);
f[0][0][0]=1;
for(i=2;i<=n;i++){
x^=1;
memset(f[x],0,sizeof(f[x]));
if(a[i]==a[i-1]) cnt++;
else{
for(j=0;j<i-1;j++){
for(k=1;k<=min(cnt,j);k++){
f[x^1][j][0]=(f[x^1][j][0]+f[x^1][j][k])%mod;
f[x^1][j][k]=0;
}
}
cnt=0;
}
for(j=0;j<i-1;j++){
for(k=0;k<=min(cnt,j);k++){
if(j>0) f[x][j-1][k]=(f[x][j-1][k]+f[x^1][j][k]*(j-k)%mod)%mod;
f[x][j][k]=(f[x][j][k]+f[x^1][j][k]*(i-2*cnt-j+2*k)%mod)%mod;
f[x][j+1][k+1]=(f[x][j+1][k+1]+f[x^1][j][k]*(2*cnt-k)%mod)%mod;
}
}
}
printf("%lld
",f[x][0][0]);
return 0;
}