[HNOI2008]Cards
Time Limit: 10 Sec Memory Limit: 162 MB
Submit: 3719 Solved: 2236
Description
小春现在很清闲,面对书桌上的N张牌,他决定给每张染色,目前小春只有3种颜色:红色,蓝色,绿色.他询问Sun有
多少种染色方案,Sun很快就给出了答案.进一步,小春要求染出Sr张红色,Sb张蓝色,Sg张绝色.他又询问有多少种方
案,Sun想了一下,又给出了正确答案. 最后小春发明了M种不同的洗牌法,这里他又问Sun有多少种不同的染色方案.
两种染色方法相同当且仅当其中一种可以通过任意的洗牌法(即可以使用多种洗牌法,而每种方法可以使用多次)洗
成另一种.Sun发现这个问题有点难度,决定交给你,答案可能很大,只要求出答案除以P的余数(P为质数).
Input
第一行输入 5 个整数:Sr,Sb,Sg,m,p(m<=60,m+1<p<100)。n=Sr+Sb+Sg。
接下来 m 行,每行描述一种洗牌法,每行有 n 个用空格隔开的整数 X1X2...Xn,恰为 1 到 n 的一个排列,
表示使用这种洗牌法,第 i位变为原来的 Xi位的牌。输入数据保证任意多次洗牌都可用这 m种洗牌法中的一种代
替,且对每种洗牌法,都存在一种洗牌法使得能回到原状态。
Output
不同染法除以P的余数
Sample Input
2 3 1
3 1 2
Sample Output
HINT
有2 种本质上不同的染色法RGB 和RBG,使用洗牌法231 一次可得GBR 和BGR,使用洗牌法312 一次 可得BRG
和GRB。
100%数据满足 Max{Sr,Sb,Sg}<=20。
思路
根据群论里brunside引理,我们可以知道:
每一种洗牌方式,都是一个闭环,我们需要选择所有的单一操作下,循环节为1的方案的方案数之和,然后在除以方案数;
需要注意的是,不洗牌也是一种操作;
对于单一操作下,循环节为1的方案的方案数,我们可以通过背包DP求得;
因为ans需要取模,同时题目有除法操作,所以还需要求逆元;
对于样例的解释:
不操作:6,(2,3,1):0,(3,1,2):0;
ans=∑c()/(m+1)=6/3=2;
代码实现
1 #include<cstdio> 2 #include<cstring> 3 const int maxn=62; 4 int n,m,sr,sb,sg,mod,ans; 5 int s[maxn]; 6 int v[maxn],size[maxn]; 7 int f[maxn][maxn][maxn]; 8 int get_c(){ 9 int cnt=0,p; 10 memset(v,0,sizeof(v)); 11 memset(size,0,sizeof(size)); 12 for(int i=1;i<=n;i++) 13 if(!v[i]){ 14 p=s[i]; 15 cnt++; 16 while(!v[p]) v[p]=1,size[cnt]++,p=s[p]; 17 } 18 memset(f,0,sizeof(f)); 19 f[0][0][0]=1; 20 for(int i=1,a=size[i];i<=cnt;i++,a=size[i]) 21 for(int r=sr;r>=0;r--) 22 for(int b=sb;b>=0;b--) 23 for(int g=sg;g>=0;g--){ 24 if(r>=a) f[r][b][g]+=f[r-a][b][g],f[r][b][g]%=mod; 25 if(b>=a) f[r][b][g]+=f[r][b-a][g],f[r][b][g]%=mod; 26 if(g>=a) f[r][b][g]+=f[r][b][g-a],f[r][b][g]%=mod; 27 } 28 return f[sr][sb][sg]; 29 } 30 int ksm(int x,int k){ 31 int ret=1; 32 while(k){ 33 if(k&1) ret*=x,ret%=mod; 34 x*=x,x%=mod; 35 k>>=1; 36 } 37 return ret; 38 } 39 int main(){ 40 scanf("%d%d%d%d%d",&sr,&sb,&sg,&m,&mod); 41 n=sr+sb+sg; 42 for(int i=1;i<=n;i++) s[i]=i; 43 ans+=get_c(),ans%=mod; 44 for(int i=1;i<=m;i++){ 45 for(int j=1;j<=n;j++) scanf("%d",&s[j]); 46 ans+=get_c(),ans%=mod; 47 } 48 ans=(ans*ksm(m+1,mod-2))%mod; 49 printf("%d\n",ans); 50 return 0; 51 }