转载请注明出处,谢谢 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=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=3441http://acm.hdu.edu.cn/showproblem.php?pid=2481
08年成都的题目,有大神会记得教我