Burnside

分析

\[S=\frac{\sum\text{每种方案对应的不动点数目}}{\text{方案}}(\text{包括不变的情况}) \]

群论中的内容(方案可能需要拆成置换)

注意,如果有0需要特判掉(除0)

例题1

ybtoj A. 【例题1】彩色项链1

旋转等价

假设原来是

\[<1,2,3,4,\cdots ,n> \]

旋转\(k\)后变成

\[<1+k,2+k,\cdots,n+k>(\% n) \]

则:

\[1=1+k=1+k+k=\cdots=1+ck(\%n),ck=0(\%n) \]

此时:

\[c_{min}=\frac{n}{(n,k)} \]

也就是说旋转\(k\)后,对于每个不动点,每种元素都有\(\frac{n}{(n,k)}\)个相同,也就是总共有\((n,k)\)个选择的可能

\[\begin{aligned} S=&\sum_{i=1}^{n}p^{(n,i)}\\=&\sum_{d|n}p^d\times \phi(\frac{n}{d}) \end{aligned} \]

时间复杂度\(O(\sqrt n lg n)\)

注意,如果有0需要特判掉(除0)

#include<bits/stdc++.h>
using namespace std;

const int N=1e7+5,M=1e6+5;
int n,p,cnt,pri[M],phi[N];
bool fl[N];
inline int ksm(int a,int b) {
	int ret=1; 
	while(b) {
		if(b&1) ret=ret*a%p;
		a=a*a%p,b>>=1;
	}
	return ret;
}
inline int Phi(int x) {
	if(x<=1e7) return phi[x];
	int tt; int ret=1;
	for(int j=1;pri[j]<=sqrt(x);j++) {
		if(x%pri[j]==0) {
			tt=pri[j]-1; x/=pri[j];
			for(;x%pri[j]==0;x/=pri[j]) tt*=pri[j]; 
			ret*=tt;
			if(x<=1e7) return phi[x]*ret; 
		}
	}
	if(x>1) ret*=(x-1);
	return ret;
}
int main() {
	int T; scanf("%d",&T);
	phi[1]=1;
	for(int i=2;i<=1e7;i++) {
		if(!fl[i]) {
			pri[++cnt]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=cnt&&pri[j]*i<=1e7;j++) {
			fl[i*pri[j]]=1;
			if(i%pri[j]==0) {
				phi[i*pri[j]]=phi[i]*pri[j];
				break;
			} else phi[i*pri[j]]=phi[i]*(pri[j]-1);
		}
	}
	while(T--) {
		scanf("%d%d",&n,&p);
		int ans=0;
		for(int i=1;i<=sqrt(n);i++) {
			if(n%i==0) {
				ans=(ans+ksm(n%p,i-1)*(Phi(n/i)%p))%p;
				if(i*i!=n) ans=(ans+ksm(n%p,n/i-1)*(Phi(i)%p))%p;
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}

例题2

ybtoj B. 【例题2】彩色项链2

翻转重合,见小本本,奇偶分类

注意,如果有0需要特判掉(除0)

#include<bits/stdc++.h>
#define ll long long
using namespace std;

int m,n;
int gcd(int x,int y) {
	return !y?x:gcd(y,x%y);
}
ll ksm(int a,int b) {
	ll ret=1;
	for(int  i=1;i<=b;i++) ret*=a;
	return ret;
}
int main() {
	while(scanf("%d%d",&m,&n),n!=0||m!=0) {
		ll ans=0;
		for(int i=1;i<=n;i++) {
			ans+=ksm(m,gcd(i,n));
		}
		if(n%2==0) ans+=(ksm(m,n/2+1)+ksm(m,n/2))*(n/2);
			else ans+=ksm(m,(n-1)/2+1)*n;
		printf("%lld\n",ans/2/n); 
	} 
	return 0;
}

例题3

Luogu P4128 [SHOI2006] 有色图

旋转方式是点的全排列,求不动点

发现边可以分成两部分:在置换中的边和两个置换中的边,分类讨论求解即可

而置换只需要知道置换的个数和置换的大小即可知道答案,所以对\(n\)自然数拆分组合数计算即可

推导部分见小本本

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int N=1e6+5;
int jc[N],inv[N],ans,g[60][60],n,m,p,a[N],mi[N];
void dfs(int t,int res,int lst,int num,int s) {
	if(!res) {
		s=(ll)s*jc[num]%p;
		ans=(ans+s)%p;
		return;
	}
	if(lst>res) return;
	int tt=0;
	if(lst)	{
		for(int j=1;j<t;j++) tt+=g[a[j]][lst];
		a[t]=lst;
		dfs(t+1,res-lst,lst,num+1,(ll)s*mi[(lst>>1)+tt]%p*inv[lst]%p);
	}
	for(int i=lst+1;i<=res;i++) {
		tt=0;
		for(int j=1;j<t;j++) {
			tt+=g[a[j]][i];
		}
		a[t]=i;
		dfs(t+1,res-i,i,1,(ll)s*mi[(i>>1)+tt]%p*inv[i]%p*jc[num]%p);
	}
}
int gcd(int x,int y) {
	return !y?x:gcd(y,x%y);
}
int main() {
	scanf("%d%d%d",&n,&m,&p);
	inv[0]=inv[1]=1;
	for(int i=2;i<=n;i++) inv[i]=(ll)(p-p/i)*inv[p%i]%p;
	jc[0]=jc[1]=1;
	for(int i=2;i<=n;i++) jc[i]=(ll)jc[i-1]*inv[i]%p;
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n;j++) g[i][j]=gcd(i,j);
	}
	mi[0]=1;
	for(int i=1;i<=1e6;i++) {
		mi[i]=(ll)mi[i-1]*m%p;	
	}
	dfs(1,n,0,0,1);
	printf("%d\n",ans);
	return 0;
}

买一送一:

Luogu P4727 [HNOI2009]图的同构计数

将有无看作01状态

例题4

Luogu P1446 [HNOI2008]Cards

注意洗牌法需要加入自己洗成自己

然后并查集搞搞DP求不动点

#include<cstdio>
#define ll long long
using namespace std;

const int N=65;
int ca,cb,cc,n,m,p,c,fa[N],num[N],w[N],f[N][N][N];
ll ans,s;

int ksm(ll a,int b) {
	ll ret=1;
	while(b) {
		if(b&1) ret=ret*a%p;
		a=a*a%p; b>>=1;
	}	
	return (int)ret;
}

int find(int x) {
	return x==fa[x]?x:fa[x]=find(fa[x]);
}

inline int ex(int x) {
	return x>=p?x-p:x;
}

int main() {
	scanf("%d%d%d%d%d",&ca,&cb,&cc,&m,&p);
	n=ca+cb+cc;
	s=ans=1;
	for(int i=2;i<=n;i++) {
		s=s*i%p;
		if(i==ca) {
			ans=ans*ksm(s,p-2)%p;
		}
		if(i==cb) {
			ans=ans*ksm(s,p-2)%p;
		}
		if(i==cc) {
			ans=ans*ksm(s,p-2)%p;
		}
	}
	ans=ans*s%p;
	for(int e=1;e<=m;e++) {
		for(int i=1;i<=n;i++) {
			fa[i]=i,num[i]=1;
		}
		for(int i=1;i<=n;i++) {
			int x; scanf("%d",&x);
			int u=find(x),v=find(i);
			if(u!=v) {
				fa[u]=v,num[v]++;
			}
		}
		c=0;
		for(int i=0;i<=ca;i++) {
			for(int j=0;j<=cb;j++) {
				for(int k=0;k<=cc;k++) {
					f[i][j][k]=0;
				}
			}
		}
		f[0][0][0]=1;
		for(int i=1;i<=n;i++) {
			if(find(i)==i) {
				for(int j=ca;j>=0;j--) {
					for(int k=cb;k>=0;k--) {
						for(int l=cc;l>=0;l--) {
							if(j>=num[i]) {
								f[j][k][l]=ex(f[j][k][l]+f[j-num[i]][k][l]);
							}
							if(k>=num[i]) {
								f[j][k][l]=ex(f[j][k][l]+f[j][k-num[i]][l]);
							}
							if(l>=num[i]) {
								f[j][k][l]=ex(f[j][k][l]+f[j][k][l-num[i]]);
						 	}
						}
					}
				}
			}
		}
		ans=ex(ans+f[ca][cb][cc]);
	}
	printf("%lld\n",ans*ksm(m+1,p-2)%p);
	return 0;
}

例题5

ybtojG. 魔法手镯

旋转的限制可以通过burnside解决

不妨设\(f[d]\)表示环大小为\(d\)的答案

\[S=\sum_{d|n}f[d]\times \phi(\frac{n}{d}) \]

推导见小本本

至于\(f[d]\),可以想想DP,发现可以矩阵优化

\[f[d]=g^d对角线的和,g表示的是矩阵 \]

#include<bits/stdc++.h>
const int p=9973;
using namespace std;

const int N=11,M=1e7+5,nn=1e6+5;
int cnt,pri[nn],phi[M],n,m,K;
bool fl[M];

struct A{
	int a[N][N];
}a,ret,g;
inline int Phi(int x) {
	if(x<=1e7) return phi[x];
	int tt; int ret=1;
	for(int j=1;pri[j]<=sqrt(x);j++) {
		if(x%pri[j]==0) {
			tt=pri[j]-1; x/=pri[j];
			for(;x%pri[j]==0;x/=pri[j]) tt*=pri[j]; 
			ret*=tt;
			if(x<=1e7) return phi[x]*ret; 
		}
	}
	if(x>1) ret*=(x-1);
	return ret;
}
inline int inv(int a,int b) {
	int ret=1; 
	while(b) {
		if(b&1) ret=ret*a%p;
		a=a*a%p,b>>=1;
	}
	return ret;
}
inline A operator *(A x, A y) {
	A z;
	for(int i=1;i<=m;i++) {
		for(int j=1;j<=m;j++) {
			z.a[i][j]=0;
			for(int k=1;k<=m;k++) {
				z.a[i][j]=(z.a[i][j]+x.a[i][k]*y.a[k][j])%p;
			}
		}
	}
	return z;
}
inline int ksm(int b) {
	for(int i=1;i<=m;i++) {
		for(int j=1;j<=m;j++) {
			ret.a[i][j]=i==j;
		}
	}
	a=g;
	while(b) {
		if(b&1) ret=ret*a;
		a=a*a; b>>=1;
	}
	int ans=0;
	for(int i=1;i<=m;i++) {
		ans=(ans+ret.a[i][i])%p;
	}
	return ans;
}
int main() {
	int T; scanf("%d",&T);
	phi[1]=1;
	for(int i=2;i<=1e7;i++) {
		if(!fl[i]) {
			pri[++cnt]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=cnt&&pri[j]*i<=1e7;j++) {
			fl[i*pri[j]]=1;
			if(i%pri[j]==0) {
				phi[i*pri[j]]=phi[i]*pri[j];
				break;
			} else phi[i*pri[j]]=phi[i]*(pri[j]-1);
		}
	}
	while(T--) {
		scanf("%d%d%d",&n,&m,&K);
		for(int i=1;i<=m;i++) {
			for(int j=1;j<=m;j++) g.a[i][j]=1;
		}
		for(int i=1;i<=K;i++) {
			int u,v; scanf("%d%d",&u,&v);
			g.a[u][v]=g.a[v][u]=0;
		}
		int ans=0;
		for(int i=1;i<=sqrt(n);i++) {
			if(n%i==0) {
				ans=(ans+ksm(i)*(Phi(n/i)%p))%p;
			//	printf("%d %d %d\n",i,ksm(i),Phi(n/i));
				if(i*i!=n) {
					ans=(ans+ksm(n/i)*(Phi(i)%p))%p;
				//	printf("%d %d %d\n",n/i,ksm(n/i),Phi(i));
				}
			} 
		}
		ans=ans*inv(n%p,p-2)%p;
		printf("%d\n",ans);
	}
	return 0;
}

例题5

ybtoj H. 周末晚会

同样,只需要改变f即可

按环处理

不妨设\(f[i][j]\)表示\(i\)个人,头一定是男生,末尾\(j\)个女生的方案,然后枚举头即可

记得一定要去掉全女生的不动点方案,最后特判是否需要加上,因为\(n\)个女生很有可能不合法

前缀和优化即可

WA了一次:忘记去掉女生的方案

#include<bits/stdc++.h>
#define ll long long
const int p=1e8+7;;
using namespace std;

const int N=2005;
int cnt,pri[N],phi[N],n,m,inv[N],f[N][N];
bool fl[N];

inline int mo(int x) {
	return x>=p?x-p:x;
}
inline int ksm(ll a,int b) {
	ll ret=1;
	while(b) {
		if(b&1) ret=ret*a%p;
		a=a*a%p,b>>=1;
	}
	return ret;
}
inline int ask(int n) {
	if(m>=n) return ksm(2,n)-1;
	int ret=0;
	for(int k=0;k<=min(n-1,m);k++) {
		ret=mo(ret+f[n-k][min(m-k,n-k-1)]);
	}
	return ret;
}
int main() { 
	int T; scanf("%d",&T);
	phi[1]=1; inv[1]=1;
	for(int i=2;i<=2000;i++) {
		if(!fl[i]) {
			pri[++cnt]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=cnt&&pri[j]*i<=2000;j++) {
			fl[i*pri[j]]=1;
			if(i%pri[j]==0) {
				phi[i*pri[j]]=phi[i]*pri[j];
				break;
			} else phi[i*pri[j]]=phi[i]*(pri[j]-1);
		}
		inv[i]=(ll)(p-p/i)*inv[p%i]%p;
	}
	while(T--) {
		scanf("%d%d",&n,&m);
		for(int i=1;i<=m;i++) f[1][i]=0;
		f[1][0]=1;
		for(int i=2;i<=n;i++) {
			f[i][0]=f[i-1][0];
			for(int j=1;j<=m;j++) {
				f[i][j]=f[i-1][j-1];
				f[i][0]=mo(f[i][0]+f[i-1][j]);
			}
		}
		for(int i=1;i<=n;i++) {
			for(int j=1;j<=m;j++) {
				f[i][j]=mo(f[i][j]+f[i][j-1]);
			}
		}
		ll ans=0;
		for(int i=1;i<=sqrt(n);i++) {
			if(n%i==0) {
				ans=(ans+(ll)ask(i)*phi[n/i]%p)%p;
				if(i*i!=n) {
					ans=(ans+(ll)ask(n/i)*phi[i]%p)%p;
				}
			} 
		}
	//	printf("%d\n",ans);
		ans=ans*inv[n]%p;
		if(m>=n) ans++; 
		printf("%lld\n",(ans+p)%p);
	}
	return 0;
}
posted @ 2021-04-15 20:33  wwwsfff  阅读(106)  评论(0编辑  收藏  举报