传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1004
这是一道Polya好题~
根据那个什么引理,本质不同的方案数等于每个置换下不同的方案数的平均值。
但是Polya定理是:
l=1|G|∑f∈Gkm(f)
而这里是有三种颜色,求每个置换里每个循环涂同样的颜色的方案数。
那么我们可以递推。设 f(i,j,k) 表示在当前置换中的第1..i个循环中,涂红色的有j张牌,涂蓝色的有k张牌,并且设 w(i) 表示第i个循环的长度。
那么枚举第i个循环被涂成什么颜色,有
f(i,j,k)=f(i−1,j,k)+f(i−1,j−w(i),k)+f(i−1,j,k−w(i))
注意 j−w(i) 和 k−w(i) 只有不小于0才能转移。
推完了取 f(n,sr,sb) 累加到答案里并取模即可。最后乘上置换数目对p的逆元就行了。
还有一个地方:因为我不知道数据有多猥琐,所以判断了一下,如果他没给1,2,3,…,n这样的置换的话,我才把它加上。
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
void exgcd(ll a,ll b,ll &d,ll &x,ll &y){
if(!b){d=a;x=1;y=0;}
else{exgcd(b,a%b,d,y,x);y-=x*(a/b);}
}
inline ll inv(ll a,ll p){
ll d,x,y;
exgcd(a,p,d,x,y);
return d==1?(x+p)%p:-1;
}
int per[61][100];
int length[61][100];
bool visit[100];
ll f[100][100][100];
int n;
ll ans;
void cut(int cur,int *a){
memset(visit,0,sizeof visit);
for(int i=1;i<=n;++i)if(!visit[i]){
int j=i,tot=0;
do{
visit[j]=true;
tot++;
j=a[j];
}while(j!=i);
length[cur][++length[cur][0]]=tot;
}
// printf("分成了%d个循环。\n",length[cur][0]);
}
int main(){
ll sr,sb,sg,m,p;
scanf("%lld%lld%lld%lld%lld",&sr,&sb,&sg,&m,&p);
n=sr+sb+sg;
bool hehe=false;
for(int i=1;i<=m;++i){
for(int j=1;j<=n;++j){
scanf("%d",per[i]+j);
}
cut(i,per[i]);
if(length[i][0]==n)hehe=1;
}
if(!hehe){
++m;
for(int i=1;i<=n;++i)per[m][i]=i;
cut(m,per[m]);
}
for(int i=1;i<=m;++i){
int *w=length[i];
memset(f,0,sizeof f);
f[1][w[1]][0]=f[1][0][w[1]]=f[1][0][0]=1;
for(int t=2;t<=w[0];++t)
for(int j=0;j<=sr;++j)
for(int k=0;k<=sb;++k){
f[t][j][k]=f[t-1][j][k];
if(j>=w[t])f[t][j][k]=(f[t][j][k]+f[t-1][j-w[t]][k])%p;
if(k>=w[t])f[t][j][k]=(f[t][j][k]+f[t-1][j][k-w[t]])%p;
}
ans=(ans+f[w[0]][sr][sb])%p;
}
ans=ans*inv(m,p)%p;
cout<<ans<<"\n";
return 0;
}