转载请注明出处,谢谢 http://blog.csdn.net/ACM_cxlove?viewmode=contents by---cxlove
最近接触了Polya计数定理,建议之前先去学习置换群。
只会皮毛,深层定理含义及证明一般书上都有。
http://poj.org/problem?id=2409
赤裸裸的Polya啊,数据范围很小,直接枚举i,对于旋转每个转换的循环节长度为gcd(i,n),对于翻转,如果为奇数,则全为n/2+1,否则一半为n/2+1,一半为n/2。第一个代码,好多地方写得好矬,在翻转的时候,循环是多余的。
#include<iostream> #include<cstdio> #include<cstring> #define LL long long using namespace std; LL gcd(LL a,LL b){ return b==0?a:gcd(b,a%b); } int c,s; LL Pow(int a,int b){ return b==0?1:Pow(a,b-1)*a; } LL polya(){ LL sum=0; for(int i=1;i<=s;i++) sum+=Pow(c,gcd(s,i)); if(s&1) sum+=s*Pow(c,(s>>1)+1); else{ for(int i=1;i<=s;i++) if(i&1) sum+=Pow(c,(s>>1)+1); else sum+=Pow(c,s>>1); } return sum/2/s; } int main(){ while(scanf("%d%d",&c,&s)!=EOF&&c+s) printf("%I64d\n",polya()); return 0; }
也是模板题,同样需要考虑翻转和旋转,不过尝试加了欧拉函数优化,之前一直是枚举i,gcd(i,n)为循环个数,而每个循环长度为L=n/gcd(i,n),我们可以直接枚举L,这个黑书上
有证明,弱菜一下子说不清楚。
普通求法: ∑n^( gcd(n,i) ) 0<=i<n 复杂度过高
优化:枚举环的长度L
枚举优化: L可以从1取到sqrt(n) ,因为L|n , n/L | n
对于每个L,我们再看有几个i满足条件
n/L = gcd(n , i)
那么令 a=n/L = gcd(n , i) , 再设i = at
那么当且仅当gcd(L,t)=1时候,才有gcd(n,i) = a
显然满足条件的i的个数就是t的个数也就是phi(L)
那么最后统计一下就是 ∑(phi(L) * N^(L-1) ) % p (L即枚举值)
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #define LL long long using namespace std; LL gcd(LL a,LL b){ return b==0?a:gcd(b,a%b); } int c,s; LL eular(int n){ LL sum=1; for(int i=2;i<=sqrt(1.0+n);i++) if(n%i==0){ sum*=(i-1); n/=i; while(n%i==0){ sum*=i; n/=i; } } if(n>1) sum*=(n-1); return sum; } LL Pow(int a,int b){ LL ret=1; while(b){ if(b&1) ret=ret*a; a=a*a; b>>=1; } return ret; } LL polya(){ LL sum=0; for(int i=1;i<=s;i++) if(s%i==0) sum+=Pow(c,i)*eular(s/i); if(s&1) sum+=s*Pow(c,(s>>1)+1); else sum+=s/2*(Pow(c,(s>>1)+1)+Pow(c,s>>1)); return sum/2/s; } int main(){ c=3; while(scanf("%d",&s)!=EOF&&s!=-1){ if(s==0) printf("0\n"); else printf("%I64d\n",polya()); } return 0; }
继续很水的一题,暴力搞搞就行。
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define MOD 1000000007 #define LL long long using namespace std; int prime[30]={2,3,5,7,11,13,17,19,23,29,31,37,41,43, 47,53,59,61,67,71,73,79,83,89,97,101},cnt=25; LL n,m; LL eular(LL num){ LL ret=1; for(int i=0;i<cnt&&prime[i]<=num;i++) if(num%prime[i]==0){ ret*=(prime[i]-1); num/=prime[i]; while(num%prime[i]==0){ num/=prime[i]; ret*=prime[i]; } } if(num>1) ret*=(num-1); return ret%MOD; } LL Pow(LL a,LL b){ LL ret=1; while(b){ if(b&1) ret=(ret*a)%MOD; a=(a*a)%MOD; b>>=1; } return ret; } LL Polya(){ LL sum=0,i; for(i=1;i*i<n;i++){ if(n%i==0){ sum=(sum+eular(i)*Pow(m,n/i))%MOD; sum=(sum+eular(n/i)*Pow(m,i))%MOD; } } if(i*i==n) sum=(sum+eular(i)*Pow(m,i))%MOD; if(n&1) sum=(sum+n*Pow(m,n/2+1))%MOD; else sum=(sum+n/2*(Pow(m,n/2)+Pow(m,n/2+1)))%MOD; return (sum*Pow(2*n,MOD-2))%MOD; } int main(){ LL t,cas=0; scanf("%I64d",&t); while(t--){ scanf("%I64d%I64d",&m,&n); printf("Case #%I64d: %I64d\n",++cas,Polya()); } return 0; }
http://poj.org/problem?id=2154
题目类似,但是数据很大,如果暴力枚举i肯定不行,必须使用欧拉函数优化,搜索n的因子
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #define LL long long #define N 1000000000 using namespace std; int n,p; bool flag[40000]={0}; int prime[40000],cnt=0; void Prime(){ for(int i=2;i<=sqrt(N+1.0);i++){ if(flag[i]) continue; prime[cnt++]=i; for(int j=2;j*i<=sqrt(N+1.0);j++) flag[i*j]=true; } } LL gcd(LL a,LL b){ return b==0?a:gcd(b,a%b); } int eular(int m){ int sum=1; for(int i=0;i<cnt&&prime[i]<=m;i++) if(m%prime[i]==0){ sum*=(prime[i]-1); m/=prime[i]; while(m%prime[i]==0){ sum*=prime[i]; m/=prime[i]; } } if(m>1) sum*=(m-1); return sum%p; } int Pow(int a,int b){ int ret=1; a=a%p; while(b){ if(b&1) ret=(ret*a)%p; a=(a*a)%p; b>>=1; } return ret; } int polya(){ int sum=0; int i=1; for(;i*i<n;i++) if(n%i==0) sum=(sum+eular(i)*Pow(n,n/i-1)+eular(n/i)*Pow(n,i-1))%p; if(i*i==n) sum=(sum+eular(i)*Pow(n,i-1))%p; return sum; } int main(){ int t; Prime(); scanf("%d",&t); while(t--){ scanf("%d%d",&n,&p); printf("%d\n",polya()); } return 0; }
http://acm.hdu.edu.cn/showproblem.php?pid=1812
还是典型的Polya,推出每种情况就行了。不过需要用大数。
旋转只有 0,90,180,270度三种旋法。
旋0度,则置换的轮换数为n*n
旋90度,n为偶数时,则置换的轮换数为n*n/4,n为奇数,则置换的轮换数为(n*n-1)/4+1
旋180度,n为偶数时,则置换的轮换数为n*n/2,n为奇数,则置换的轮换数为(n*n-1)/2+1
旋270度,n为偶数时,则置换的轮换数为n*n/4,n为奇数,则置换的轮换数为(n*n-1)/4+1
反射 沿对角反射两种,沿对边中点连线反射两种
n为偶数时,沿对边中点连线反射两种的置换轮换数为 n*n/2
沿对角反射两种的置换轮换数为 (n*n-n)/2+n
n为奇数时,沿对边中点连线反射两种的置换轮换数为 (n*n-n)/2+n
沿对角反射两种的置换轮换数为 (n*n-n)/2+n
综上所述:用m种颜色给n*n矩形染色的方案数L
S=m^(n*n)+m^((n*n+3)/4)+m((n*n+1)/2)+m^((n*n+3)/4) (考虑旋转时)
n为奇数L= (S+4*m^((n*n+n)/2)) / 8
n为偶数L= (S+2*m^((n*n+n)/2)+2*m(n*n/2) )/ 8
http://acm.hdu.edu.cn/showproblem.php?pid=3547
对正方体的8个顶点染色,24种情况,理清楚就没问题了
最后的结果是ans=17*x^2+6*x^4+x^8
不过也得用大数,不会JAVA的伤不起呐
http://poj.org/problem?id=2888
这个和之前的不一样,给出K组限制关系,a和b不能相邻,借鉴10个经典矩阵问题,构造矩阵,快速幂乘,由于是一个循环,比如说最开始是a->b->->c->d->a,最后还是要回到a的,所以最终的解是矩阵的对角和。经典题目啊,赞一个
/* ID:cxlove PROB:poj 2888 Magic Bracelet DATA:2012.4.11 HINT:有限制的Polya,矩阵快速幂乘,取模逆元,各种经典 */
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #define LL long long #define N 1000000000 #define MOD 9973 using namespace std; int n,m,p,k; bool flag[40000]={0}; int prime[40000],cnt=0; struct matrix{ int m[15][15]; }mat; matrix operator*(matrix m1,matrix m2){ matrix ans; for(int i=1;i<=m;i++) for(int j=1;j<=m;j++){ ans.m[i][j]=0; for(int k=1;k<=m;k++) ans.m[i][j]=(ans.m[i][j]+m1.m[i][k]*m2.m[k][j])%MOD; } return ans; } matrix operator^(matrix m1,int num){ matrix ans; memset(ans.m,0,sizeof(ans.m)); for(int i=1;i<=m;i++) ans.m[i][i]=1; while(num){ if(num&1) ans=ans*m1; m1=m1*m1; num>>=1; } return ans; } void Prime(){ for(int i=2;i<=sqrt(N+1.0);i++){ if(flag[i]) continue; prime[cnt++]=i; for(int j=2;j*i<=sqrt(N+1.0);j++) flag[i*j]=true; } } int eular(int m){ int sum=1; for(int i=0;i<cnt&&prime[i]<=m;i++) if(m%prime[i]==0){ sum*=(prime[i]-1); m/=prime[i]; while(m%prime[i]==0){ sum*=prime[i]; m/=prime[i]; } } if(m>1) sum*=(m-1); return sum%MOD; } int Pow(int a,int b){ int ret=1; a=a%MOD; while(b){ if(b&1) ret=(ret*a)%MOD; a=(a*a)%MOD; b>>=1; } return ret; } void debug(matrix t){ for(int i=1;i<=m;i++){ for(int j=1;j<m;j++) printf("%d ",t.m[i][j]); printf("%d\n",t.m[i][m]); } } int slove(int num){ matrix temp=mat^num; int ret=0; for(int i=1;i<=m;i++) ret=(ret+temp.m[i][i])%MOD; return ret; } int polya(){ int sum=0; int i=1; for(;i*i<n;i++) if(n%i==0) sum=(sum+eular(i)*slove(n/i)+eular(n/i)*slove(i))%MOD; if(i*i==n) sum=(sum+eular(i)*slove(i))%MOD; return (sum*Pow(n%MOD,MOD-2))%MOD; } int main(){ int t; Prime(); scanf("%d",&t); while(t--){ scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=m;i++) for(int j=1;j<=m;j++) mat.m[i][j]=1; while(k--){ int a,b; scanf("%d%d",&a,&b); mat.m[a][b]=mat.m[b][a]=0; } printf("%d\n",polya()); } return 0; }
http://acm.hdu.edu.cn/showproblem.php?pid=2865AC神的题目?看上去和上一题类似,可是这里的颜色数量好多呐,不可能用矩阵了,但是考虑到矩阵的对角线全为0,其余全为1,可以推导出公式。
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #define LL long long #define N 1000000000 #define MOD 1000000007 using namespace std; LL n,m,k; bool flag[40000]={0}; int prime[40000],cnt=0; void Prime(){ for(int i=2;i<=sqrt(N+1.0);i++){ if(flag[i]) continue; prime[cnt++]=i; for(int j=2;j*i<=sqrt(N+1.0);j++) flag[i*j]=true; } } int eular(int m){ int sum=1; for(int i=0;i<cnt&&prime[i]<=m;i++) if(m%prime[i]==0){ sum*=(prime[i]-1); m/=prime[i]; while(m%prime[i]==0){ sum*=prime[i]; m/=prime[i]; } } if(m>1) sum*=(m-1); return sum%MOD; } LL Pow(LL a,LL b){ LL ret=1; a=a%MOD; while(b){ if(b&1) ret=(ret*a)%MOD; a=(a*a)%MOD; b>>=1; } return ret; } LL slove(LL p,LL k){ LL ans=Pow(p-1,k); if(k&1) ans=(ans+MOD-(p-1))%MOD; else ans=(ans+p-1)%MOD; return ans; } int polya(){ int sum=0; int i=1; for(;i*i<n;i++) if(n%i==0) sum=(sum+eular(i)*slove(m-1,n/i)+eular(n/i)*slove(m-1,i))%MOD; if(i*i==n) sum=(sum+eular(i)*slove(m-1,i))%MOD; return (sum*Pow(n%MOD,MOD-2))%MOD; } int main(){ Prime(); while(scanf("%I64d%I64d",&n,&m)!=EOF) printf("%d\n",(m*polya())%MOD); return 0; }
http://acm.hdu.edu.cn/showproblem.php?pid=3441传说中的AC的“身体”,果断有待研究
http://acm.hdu.edu.cn/showproblem.php?pid=2481
08年成都的题目,有大神会记得教我