[CF840C] On the Bench
问题描述
给定一个序列 \(a(a_i\le 10^9)\),长度为 \(n(n\le 300)\)。
试求有多少 1 到 \(n\) 的排列 \(p_i\),满足对于任意的 \(2\le i\le n\) 有 \(a_{p_{i-1}}\times 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}\times (2*cnt-k)\)。
- 放在某两个相邻的数中间,但不跟自己相同。\(f_{i,j-1,k}=f_{i-1,j,k}\times (j-k)\)。
- 放在了其他地方。\(f_{i,j,k}=f_{i-1,j,k}\times [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\n",f[x][0][0]);
return 0;
}