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