CF840C On the Bench
题意翻译
给定\(n\) \((1≤n≤300)\) 个数,求问有多少种排列方案使得任意两个相邻的数之积都不是完全平方数。由于方案数可能很大,输出方案数 \(mod\) \(10^9+7\)的值。
首先每个数的每个质因子的幂可以先\(\bmod 2\),然后问题转化成两两不相等的方案数了。
证明也很简单,这些质因子的2次方是没有用的,洛谷的题解有一种更加美妙的方法进行了解释。
然后把每一组相等的数划分到一个集合里去。
设\(dp_{i,j}\)表示前\(i\)个集合有\(j\)对相等位置的排列数。
给出填表法的转移方程再进行具体解释
\(dp_{i,j+l-k}+=dp_{i-1,j} \times (fac_{ct} \times \binom{ct-1}{ct-1-l}) \times (\binom{j}{k} \times \binom{st+1-j}{ct-l-k})\)
两个括号的内容是独立考虑的,有些变量的意义慢慢说。
首先考虑第一个括号的内容
\(l\)表示第\(i\)个集合自身产生了\(l\)个相等的数,\(ct\)是第\(i\)个集合元素的个数,\(fac\)是阶乘
\(\binom{ct-1}{ct-1-l}\)表示把\(st\)个有序元素分成\(st-l\)个有序集合的方案数
可以从类似插板法的方法来说明,就是往空挡里面插东西。
因为这样的集合划分是有序的,而我们每个元素是可以无序的,所以乘上排列数,也就是阶乘。
所以第一个括号就是求出了把\(i\)这个集合分成了\(ct-l\)个集合的方案数,同时,Ta们是有序的。
第二个括号是把这\(ct-l\)个集合往空挡里面插的方案数。
\(k\)表示有\(k\)对相等的位置被插开了,\(st\)表示前\(i-1\)个集合的总元素个数
于是我们把这些东西分开的去插开别人或者不插开别人的方案数乘起来就可以了。
要枚举\(k\)和\(l\),复杂度说不清楚\(\text{QAQ}\)
Code:
#include <cstdio>
#include <algorithm>
#define ll long long
const int N=300;
const ll mod=1e9+7;
int a[N+10],b[N+10],cnt[N+10],tt[N+11],n,m;
ll C[N+10][N+10],dp[N+10][N+10],fac[N+10];
void init()
{
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;
}
fac[0]=1;
for(int i=1;i<=N;i++)
fac[i]=fac[i-1]*i%mod;
}
int min(int x,int y){return x<y?x:y;}
int main()
{
init();
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
for(int j=2;j*j<=a[i];j++)
while(a[i]%(j*j)==0) a[i]/=j*j;
}
std::sort(a+1,a+1+n);
for(int i=1;i<=n;i++)
{
if(a[i]==b[m]) ++cnt[m],++tt[m];
else b[++m]=a[i],cnt[m]=1,tt[m]=tt[m-1]+1;
}
dp[1][cnt[1]-1]=fac[cnt[1]];
for(int i=1;i<m;i++)
for(int j=0;j<tt[i];j++)//相等个数
{
if(!dp[i][j]) continue;
for(int k=0;k<=min(j,cnt[i+1]);k++)//堵几个?
for(int l=0;l<=cnt[i+1]-k;l++)//搞几个?
{
if(j+l-k>=tt[i+1]||j+l-k<0) continue;
(dp[i+1][j+l-k]+=
dp[i][j]*C[j][k]%mod*
C[tt[i]+1-j][cnt[i+1]-l-k]%mod*
fac[cnt[i+1]]%mod*
C[cnt[i+1]-1][cnt[i+1]-1-l]%mod)%=mod;
}
}
printf("%lld\n",dp[m][0]);
return 0;
}
2018.10.15