【HNOI2008】Cards BZOJ 1004
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。
思路
嘛~首先可以看到是组合数学的题目。之后就想大概是个什么模型。
看到在一个排列中元素可以交换,就想到了置换群,Polya计数法。
那么就是有这么多给你的置换方法,求本质上不同的排列有多少。我们将每一个置换群分解,找出置换的循环节:
比如置换1 4 3 5 2可以分解为(1)(2 4 5)(3)这三个循环节。
由polya计数法得:ans=所有置换中本质相同的方案的平均数。
看到100%数据满足 Max{Sr,Sb,Sg}<=20可以用dp做。
用f[i][j][k]表示用了i个红色,j个绿色,k个蓝色的方案数,则f[i][j][k]=f[i-size][j][k]+f[i][j-size][k]+f[i][j][k-size](size是当前循环节的大小)
最后不要忘记加上(1 2 3 ....n)这个循环,这个可以直接算出有N!/(sr!*sg!*sb!)
然后除以m+1,p为质数,用逆元算。
1 #include <iostream> 2 #include <cstring> 3 #include <string> 4 #include <cstdio> 5 #include <cstdlib> 6 #include <cmath> 7 #include <algorithm> 8 #include <queue> 9 #include <stack> 10 #include <map> 11 #include <set> 12 #include <list> 13 #include <vector> 14 #include <ctime> 15 #include <functional> 16 #define pritnf printf 17 #define scafn scanf 18 #define For(i,j,k) for(int i=(j);i<=(k);(i)++) 19 #define Clear(a) memset(a,0,sizeof(a)) 20 using namespace std; 21 typedef long long LL; 22 typedef unsigned int Uint; 23 const int INF=0x7fffffff; 24 //==============struct declaration============== 25 26 //==============var declaration================= 27 const int MAXN=30; 28 int sr,sb,sg,n,m,p,MOD,cnt=0; 29 int ans=0; 30 int tran[MAXN*5]; 31 int f[MAXN][MAXN][MAXN]; 32 bool vis[MAXN*5]; 33 //==============function declaration============ 34 int quickpow(int a,int Exp); 35 int inv(int a){return quickpow(a,MOD-2);} 36 int fact(int x){int ans=1;for(int i=1;i<=x;i++) ans=(ans*i)%MOD;return ans;} 37 //==============main code======================= 38 int main() 39 { 40 //#define FILE__ 41 #ifdef FILE__ 42 freopen("input.txt","r",stdin); 43 freopen("output.txt","w",stdout); 44 #endif 45 scanf("%d%d%d%d%d",&sr,&sb,&sg,&m,&MOD);n=sr+sb+sg; 46 for(int i=1;i<=m;i++){ 47 for(int j=1;j<=n;j++) 48 scanf("%d",tran+j); 49 memset(vis,false,sizeof(vis));memset(f,0,sizeof(f)); 50 f[0][0][0]=1;cnt=0; 51 for(int p=1;p<=n;p++){ 52 if (vis[p]) continue; 53 cnt++; 54 int siz=0,x=p; 55 while (!vis[x]){ 56 vis[x]=true; 57 x=tran[x]; 58 siz++; 59 }//找出循环节 60 for(int i=sr;i>=0;i--) 61 for(int j=sb;j>=0;j--) 62 for(int k=sg;k>=0;k--){ 63 if (i>=siz) f[i][j][k]+=f[i-siz][j][k]; 64 if (j>=siz) f[i][j][k]+=f[i][j-siz][k]; 65 if (k>=siz) f[i][j][k]+-f[i][j][k-siz]; 66 f[i][j][k]%=MOD; 67 } //DP 68 } 69 ans=(ans+f[sr][sb][sg])%MOD; 70 } 71 ans=ans+fact(n)*inv(fact(sr))*inv(fact(sb))*inv(fact(sg)); 72 printf("%d\n",(ans*inv(m+1))%MOD); 73 return 0; 74 } 75 //================fuction code==================== 76 int quickpow(int a,int Exp){ 77 if (Exp==0) return 1; 78 if (Exp==1) return a; 79 int temp=quickpow(a,Exp/2); 80 temp=(temp*temp)%MOD; 81 if (Exp&1) temp=(temp*a)%MOD; 82 return temp; 83 }