[AHOI2018初中组]球球的排列
真是一道毒瘤的好题!
step1 预处理
设有三个正整数
所以我们得到,若
那么我们将题意转化为:
有
m 种颜色的球,第i 种颜色的球有s_i 个,求同色不相邻的排列的数量。
<!-- more -->
最后将球按颜色排序,本题的预处理就完成了。
step2 DP
本题的毒瘤之处就在与状态和转移。
先看状态定义:
看到这里晕头转向的同学,这里有图让你清醒:
容易得到答案就是
下面为大家一条一条讲解状态转移方程。
转移1
第
i 个球与第i-1 个球颜色不同,将该球放于两个异色球之间(包括排列的开头和结尾)。
因为我们之前已经按颜色排序了,所以第
所以
转移2
第
i 个球与第i-1 个球颜色不同,将该球放于两个同色球之间。
如下图,
所以
转移3
第
i 个球与第i-1 个球颜色相同,将该球放于与该球颜色相同的球旁边。
为了方便,设到第
所以
转移4
第
i 个球与第i-1 个球颜色相同,将该球放于两个同色球之间。
如下图,
所以
转移5
第
i 个球与第i-1 个球颜色相同,将该球放于两个异色球之间。
如下图,
所以
step3 复杂度、细节与代码
本题的时间复杂度为
Code:
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<algorithm>
#define ll long long
int rd(){
int k=0;char c=getchar();
while(c>'9'||c<'0')c=getchar();
while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
return k;
}
const int N=301,M=1000000007;
int n,a[N],b[N],now=1,pre,cnt,f[2][N][N];//now,pre表示i和i-1的状态
bool sqr(ll x){ll S=sqrt(x);return S*S==x;}
int main(){
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
n=rd();
for(int i=1;i<=n;++i){
a[i]=rd(),b[i]=i;
for(int j=1;j<i;++j)if(sqr((ll)a[i]*a[j])){b[i]=j;break;}
}//实际我们并不需要并查集,只用将父亲设为第一个匹配成完全平方数的数
std::sort(b+1,b+n+1),f[0][0][0]=1;//初始化
for(int i=1;i<=n;++i){
memset(f[now],0,sizeof(f[now]));//滚动数组清空
if(b[i]!=b[i-1]){
cnt=0;//将与i相同的数的个数清空
for(int j=0;j<i;++j)
for(int k=0;k<=j+1;++k){//这里本来要枚举j',为了方便写作k
if(k<=j)f[now][j][0]=(f[now][j][0]+(ll)f[pre][k][j-k]*(i-j))%M;//注意边界
f[now][j][0]=(f[now][j][0]+(ll)f[pre][k][j-k+1]*(j+1))%M;
}
}
else{
for(int j=0;j<i;++j)
for(int k=0;k<=cnt;++k){
if(k>0)f[now][j][k]=(f[now][j][k]+(ll)f[pre][j][k-1]*(cnt*2-k+1))%M;//注意边界
f[now][j][k]=(f[now][j][k]+(ll)f[pre][j+1][k]*(j+1))%M;
f[now][j][k]=(f[now][j][k]+(ll)f[pre][j][k]*(i-cnt*2+k-j))%M;
}
}
now^=1,pre^=1,++cnt;//滚动
}
printf("%d\n",f[pre][0][0]);
return 0;
}
step4 进阶算法
时隔一年重看这题,终于学会了容斥的
记颜色数为
此时第
在计算时需要枚举所有的满足
我们可以发现,有许多方案被错误计算了,如 1 1 2 这种方案会被
接下来只用考虑如何计算
考虑时间复杂度,虽然有三重循环,但循环次数为
Code:
#include<stdio.h>
#include<math.h>
typedef long long ll;
int rd(){
int k=0;char c=getchar();
while(c>'9'||c<'0')c=getchar();
while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
return k;
}
const int N=301,M=1000000007;
bool sqr(ll x){ll s=sqrt(x);return s*s==x;}
int Pow(int x,int a){
int s=1;
while(a){
if(a&1)s=(ll)s*x%M;
x=(ll)x*x%M,a>>=1;
}
return s;
}
int n,m,ans,a[N],s[N],fac[N],inv[N],f[N][N];
int C(int n,int m){return(ll)fac[n]*inv[m]%M*inv[n-m]%M;}
int main(){
n=rd(),fac[0]=inv[0]=f[0][0]=1;
for(int i=1;i<=n;++i){
int _=rd();fac[i]=(ll)fac[i-1]*i%M;
for(int j=1;j<=m;++j)if(sqr((ll)_*a[j])){++s[j];goto End;}
a[++m]=_,++s[m];End:;
}
inv[n]=Pow(fac[n],M-2);
for(int i=n-1;i;--i)inv[i]=(ll)inv[i+1]*(i+1)%M;
for(int i=1,_=0;i<=m;++i){
_+=s[i];
for(int j=1;j<=_;++j)for(int k=1;k<=s[i]&&k<=j;++k)
f[i][j]=(f[i][j]+(ll)f[i-1][j-k]*C(s[i]-1,k-1)%M*inv[k])%M;
}
for(int i=m;i<=n;++i){
int t=(ll)f[m][i]*fac[i]%M;
if((n-i)&1)ans=(ans+M-t)%M;
else ans=(ans+t)%M;
}
for(int i=1;i<=m;++i)ans=(ll)ans*fac[s[i]]%M;
printf("%d\n",ans);
return 0;
}