Burnside引理与Polya定理

简述:

Burnside 引理:

一类状态在一个置换群 (\(G\)) 的作用下本质不同的状态(不同等价类)个数

\(∣X/G∣=\dfrac{1}{|G|}*\sum\limits_{r\in G}c(r)\)

\(c(r)\)表示在 \(r\) 这一置换作用下不动点(状态不变的方案)的个数。

证明略。

Polya定理:

\(c(r)=k^m\)

\(k\) 为当前置换不同循环的个数,\(m\)为可赋予颜色数。

即位于同一循环(可互相替代,专业叫等价类)的元素颜色相同才能成为不动点。

栗子:

不同的翻转操作即为置换群。

对于向下 $90^\circ $ 的操作,中间四个面,左边的面,右边的面即为三个不同的循环(等价类)。

例题:

一:P4980 【模板】Pólya 定理

题意:

给定一个 \(n\) 个点,\(n\) 条边的环,有 \(n\) 种颜色,给每个顶点染色,问有多少种本质不同的染色方案。

做法:

咳咳,不是我懒得写,主要 Latex 太难打了。

说明一下那个 \(gcd\) 那点:

对于一个旋转 \(k\) 次的操作,当前点旋转 \(n/gcd(n,k)\) 次就会回到当前点,即 \(k * ( n / gcd ( n,k ) ) %n == 0\) 。因此一个循环有 \(n/gcd(n,k)\) 节点,所以一共有 \(gcd(n,k)\) 个循环。

code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e5+8;
ll n;
const ll mod=1e9+7;
ll qm(ll x,int p)
{
	if(p==0) return 1;
	ll tmp=qm(x,p>>1);
	if(p&1) return tmp*tmp%mod*x%mod;
	return tmp*tmp%mod;
}
ll read()
{
	ll x=0;
	char c=getchar();
	while(c>'9'||c<'0') c=getchar();
	while(c>='0'&&c<='9'){
		x=x*10;
		x+=c-'0';
		c=getchar();
	}
	return x;
}
ll ol(ll x) 
{
  ll ans=x;
  for(int i=2;i*i<=x;i++)
    if(x%i==0){
      ans=ans*(i-1)%mod*qm(i,mod-2)%mod;
      while(x%i==0) x/=i;
    }
  if(x>1) ans=ans*(x-1)%mod*qm(x,mod-2)%mod;
  return ans%mod;
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		n=read();
		ll ans=0;
		for(ll i=1;i*i<=n;i++)
		{
			if(n%i) continue;
			ans=(ans+ol(n/i)*qm(n,i-1)%mod)%mod;
			if(i*i!=n) ans=(ans+ol(i)*qm(n,n/i-1)%mod)%mod;
		}
		printf("%lld\n",ans);
	}
}

还有道一样的题:【例题1】彩色项链1

【例题2】彩色项链2

改成 \(m\) 种颜色还是一样的,只是改一下对应底数就行,翻转的操作也较好处理。

还有一道:P5233 [JSOI2012]爱之项链

本题还不清楚为啥 T 了。乱开longlong害死人。

关于波利亚定理的运用是一样的,只是多了个 \(dp\) 和数学求斐波那契数列一类的通项公式的方法,后者还没学会,不过可以用矩阵代替。

*二:P4727 [HNOI2009]图的同构计数

题意:

边有两种颜色,
求含 N 个点的图在同构意义下不同构的图的数目。

A图与B图被认为是同构的是指:A图的顶点经过一定的重新标号以后,A图的顶点集和边集要完全与B图一一对应。

做法:

对于自己来说可能更清楚的讲解:

一个图的置换,其实就是找一种排列来替换 \(1\thicksim n\)的点。

对于一个置换,将其拆为若干个互不影响的置换。

2 3 1

1 2 3

以上置换代表用 2 3 1 顶替 1 2 3。

例如:

5 3 2 1 4

1 2 3 4 5

将其拆为

5 1 4

1 4 5

3 2

2 3

下面将边分为两类。

一:两端点在同一置换里。

对于每个置换而言,将其按顺序排好

例如
3 4 2 1

1 2 3 4

按 1 4 2 3 的顺序排(因为元素是按这个顺序流动的)。

排成一个正多边形,会发现不同长度(即相隔不同个数的点)的边属于不同的等价类,因此有 \(\left\lfloor\dfrac{p[i]}{2}\right\rfloor\) 种不同的等价类,\(p[i]\) 为该置换点数。

二:两端点在不同置换里。

一条边想转回自己的位置要走 \(lcm(p[i],p[j])\) 下,即两点同时回到原位,因此这 \(lcm(p[i],p[j])\) 条边属于一个等价类,共有 \(p[i]*p[j]\) 条边,因此有 \(p[i]*p[j]/lcm(p[i],p[j])=gcd(p[i],p[j]\))。

综上答案为:

\(b\)\(p\)\(K\)为拆分出来的置换个数。

最后,为了使计算快捷,直接枚举(爆搜)本质不同的置换,即将 \(n\) 拆分。

考虑有多少种拆成 \(P\) 这种方案的。

\(\dfrac{n!}{\prod_{p[i]!}}\) 算出点的分配方案。

对于一个置换里的点,相当于一个 \(n\) 边型顶点的排列,即圆排列,因此有对于一个置换来说有 \((p[i]-1)!\) 种排列方案,总的就要乘上 \(\prod_{(p[i]-1)!}\)

最后,对于长度相等的循环它们之间可以彼此交换,本质上是一样的,因此还是会算重。设 \(c\) 表示表某个长度的循环的个数,则会算重 \(c!\) 倍。因此答案还需除以 \(\prod_{c[i]!}\)

算上除以的置换群大小 \(n!\),总答案为:

黑题完成!

code:

#include<bits/stdc++.h>
using namespace std;
const int N=65;
const int mod=997;
int n;
int p[N],tot;
int t[N];
int jc[N];
int ans=0;
int minn(int a,int b)
{
	return a>b?b:a;
}
int maxx(int a,int b)
{
	return a<b?b:a;
}
int qm(int x,int p)
{
	if(p==0) return 1;
	int tmp=qm(x,p>>1);
	if(p&1) return tmp*tmp*x%mod;
	return tmp*tmp%mod;
}
int gcd(int a,int b)
{
	if(b==0) return a;
	return gcd(b,a%b);
}
void work()
{
	int k=0;
	int tmp=1;
	memset(t,0,sizeof(t));
	for(int i=1;i<=tot;i++)
	{
		k+=p[i]/2;
		t[p[i]]++;
		tmp=tmp*qm(p[i],mod-2)%mod;
		for(int j=1;j<i;j++)
		k+=gcd(p[i],p[j]);
	}
	for(int i=1;i<=n;i++)
	{
		if(t[i]>1)
		tmp=tmp*qm(jc[t[i]],mod-2)%mod;
	}
	ans=(ans+qm(2,k)*tmp%mod)%mod;
}
void dfs(int x,int h,int s)
{
    if(!h) tot=x-1,work();
    if(h<s) return;
    for(int i=s;i<=h;i++)
	{
        p[x]=i;
        dfs(x+1,h-i,i);
    }
}
int main()
{
	cin>>n;
	jc[0]=1;
	for(int i=1;i<=n;i++)
	jc[i]=jc[i-1]*i%mod;
	dfs(1,n,1);
	cout<<ans<<endl;
}

如果有 \(m\) 种颜色也是一样的:
P4128 [SHOI2006] 有色图

卡常所得经验:

\(1\).深搜别闲着没事写迭代加深,慢死了,这两题的深搜可以多看一看。

\(2\).逆元可以预处理一下快一点。

黑题二倍经验,好耶!

G. 魔法手镯

题意:

有一个 \(n\) 颗魔法珠子组成的魔法手镯。有\(m\) 种不同的魔法珠。每一种珠子都是无限的。将许多珠子串在一起,就可以制成一个美丽的圆形魔法手镯。某些种类的珠子会相互作用并爆炸,你必须非常小心以确保这些珠子不会串在一起。

做法:

公式套上矩阵乘法。

有多少个等价类相当于将多少个珠子串成环,枚举第一个位置放什么颜色,后面用矩阵优化 \(DP\) 即可。

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=9973;
int n,m,k;
int ans;
struct mt{
	int n,m;
	int a[15][15];
};
mt tp;
mt operator * (const mt x,const mt y)
{
	mt c;
	c.n=x.n;
	c.m=y.m;
	for(int i=1;i<=c.n;i++)
	for(int j=1;j<=c.m;j++){
		c.a[i][j]=0;
		for(int k=1;k<=x.m;k++)
		c.a[i][j]=(c.a[i][j]+x.a[i][k]*y.a[k][j])%mod;
	}
	return c;
}
int qm(int x,int p)
{
	if(p==0) return 1;
	int tmp=qm(x,p>>1);
	if(p&1) return tmp*tmp*x%mod;
	return tmp*tmp%mod;
}
int gcd(int a,int b)
{
	if(b==0) return a;
	return gcd(b,a%b);
}
mt mq(int k)
{
	mt p;
	p.n=p.m=m;
	for(int i=1;i<=m;i++)
	for(int j=1;j<=m;j++)
	p.a[i][j]=(i==j);
	mt q=tp;
	while(k)
	{
		if(k&1) p=p*q;
		q=q*q;k>>=1;
	}
	return p;
}
int work(int k)
{
	mt p,pp;
	p.n=m;p.m=1;
	for(int i=1;i<=m;i++) p.a[i][1]=0;
	int num=0;
	for(int i=1;i<=m;i++)
	{
		pp=p,pp.a[i][1]=1,pp=mq(k-1)*pp;
		for(int j=1;j<=m;j++) num=(num+pp.a[j][1]*tp.a[i][j])%mod;
	}
	return num;
}
int ol(int x)
{
	int p=x;
	for(int i=2;i*i<=x;i++)
	{
		if(x%i) continue;
		p=p*(i-1)%mod*qm(i,mod-2)%mod;
		while(x%i==0) x/=i;
	}
	if(x>1) p=p*(x-1)%mod*qm(x,mod-2)%mod;
	return p;
}
signed main()
{
	int T;
	cin>>T;
	while(T--)
	{
		scanf("%lld%lld%lld",&n,&m,&k);
		tp.n=tp.m=m;
		for(int i=1;i<=m;i++)
		for(int j=1;j<=m;j++)
		tp.a[i][j]=1;
		while(k--)
		{
			int x,y;
			scanf("%lld%lld",&x,&y);
			tp.a[x][y]=tp.a[y][x]=0;
		}
		ans=0;
		for(int i=1;i*i<=n;i++)
		{
			if(n%i) continue;
			ans=(ans+work(i)*ol(n/i))%mod;
			if(i*i!=n) ans=(ans+work(n/i)*ol(i))%mod;
		}
		ans=ans*qm(n,mod-2)%mod;
		printf("%lld\n",ans);
	}
}

H. 周末晚会

题意:

\(n\) 个人围绕着圆桌坐着,其中一些是男孩,另一些是女孩。你的任务是找出所有合法的方案数,使得不超过 \(k\) 个女孩座位是连续的。循环同构会被认为是同一种方案。

做法:

先套公式,难点在于如何求一个置换下不动点的个数。

\(f[i][j]\) 表示长度为 \(i\) 第一个为男生最后有连续 \(j\) 个女生的方案数。

枚举合法长度乘上合法位置数即可。注意特判全为女生的情况。

code:

#include<bits/stdc++.h>
using namespace std;
#define ll unsigned long long
const int mod=1e8+7;
const int N=2005;
int n,k;
ll f[N][N];
ll qm(ll x,int p)
{
	if(p==0) return 1;
	ll tmp=qm(x,p>>1);
	if(p&1) return tmp*tmp%mod*x%mod;
	return tmp*tmp%mod;
}
int gcd(int a,int b)
{
	if(b==0) return a;
	return gcd(b,a%b);
}
ll work(int p)
{
	ll num=(n==k);
	for(ll i=p>k?p-k:1;i<=p;i++)
	num=(num+f[i][0]*(p-i+1)%mod)%mod;
	return num;
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		memset(f,0,sizeof(f));
		scanf("%d%d",&n,&k);
		if(k>n) k=n;
		f[1][0]=1;
		for(int i=2;i<=n;i++)
		{
			for(int j=0;j<min(k,i);j++)
			f[i][j+1]=f[i-1][j],f[i][0]=(f[i][0]+f[i-1][j])%mod;
			f[i][0]=(f[i][0]+f[i-1][k])%mod;
		}
		ll ans=0;
		for(int i=1;i<=n;i++)
		ans=(ans+work(gcd(i,n)))%mod;
		printf("%lld\n",ans*qm(n,mod-2)%mod);
	}
}

垃圾题目把模数写错了浪费我半天时间!!!

posted @ 2022-01-18 19:58  ☄️ezuyz☄️  阅读(292)  评论(0)    收藏  举报