Polya定理小结

转载请注明出处,谢谢 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;
}

http://poj.org/problem?id=1286

也是模板题,同样需要考虑翻转和旋转,不过尝试加了欧拉函数优化,之前一直是枚举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;
}

http://acm.hdu.edu.cn/showproblem.php?pid=3923

继续很水的一题,暴力搞搞就行。


#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=2865

AC神的题目?看上去和上一题类似,可是这里的颜色数量好多呐,不可能用矩阵了,但是考虑到矩阵的对角线全为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年成都的题目,有大神会记得教我




  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值