Burnside and polya

Burnside 与 Polya

推导过程:

部分题目

模板 Polya 定理

给定 \(n\) 个点 \(n\) 条边的环,\(m\) 种颜色,问本质不同染色方案数,两个方案本质不同,当且仅当对于所有的循环串都不同

直接 \(Burnside\)

考虑到给长为 \(d\) 的链染色的本质不同方案数是 \(m^d\),套用 \(Burnside\) 有:

\[\begin{aligned} res&=\frac{1}{n}\sum_{i=1}^nm^{\gcd(i,n)}\\ &=\frac{1}{n}\sum_{d|n}m^d\sum_{k=1}^n[\gcd(k,n)=d]\\ &=\frac{1}{n}\sum_{d|n}m^d\sum_{k=1}^{\frac{n}{d}}[\gcd(i,\frac{n}{d})=1]\\ &=\frac{1}{n}\sum_{d|n}m^d\varphi(\frac{n}{d}) \end{aligned} \]

Tip:计算所有 \(d|n\)\(\varphi(d)\) 的值:

\(n\) 质因数分解,dfs 枚举使用当前质因子 \(p_i\) 的个数 \(c_i\),进而由欧拉函数定义式可以求出其欧拉函数值。

复杂度 \(O(d(n))\) 不过暴力是可以过的

code
#include<bits/stdc++.h>
using namespace std;
#define N 1050500
#define int long long 
const int p=1e9+7;
int phi(int n){
	int res=n;
	for(int i=2;i*i<=n;i++){
		if(n%i==0)res/=i,res*=(i-1);
		while(n%i==0)n/=i;
	}
	if(n>1)res=res*(n-1)/n;
	return res;
}
int power(int a,int b){
	int ans=1;
	while(b){
		if(b&1)ans=1ll*ans*a%p;
		a=1ll*a*a%p;
		b>>=1; 
	}
	return ans;
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int t;cin>>t;
	while(t--){
		int n;cin>>n;int t=sqrt(n)+1;
		int res=0,inv=power(n,p-2);
		for(int i=1;i<=t;i++){
			if(n%i==0){
				res+=1ll*power(n,i)*phi(n/i)%p;
				res%=p;
				if(n!=i*i){
					int d=n/i;
					res+=1ll*power(n,d)*phi(i)%p;res%=p;
				}
			}
		}
		res=1ll*res*inv%p;res=(res%p+p)%p;
		cout<<res<<"\n";
	}
}

[MtOI2018] 魔力环

求有多少个本质不同的 01 环,满足有恰有 \(m\)\(1\),且没有一段 \(1\) 长度超过 \(k\)\(n\le 10^5\)

定义 \(f(n,m)\) 为用 \(m\)\(1\),组成长为 \(n\) 的环的合法环个数。两个方案不同,仅考虑 \([1,n]\) 这个序列不同。

根据 \(Burnside\) 有:

\[res=\frac{1}{n}\sum_{i=1}^n[\frac{n}{d}|m]f(d,\frac{m}{\frac{n}{d}})\varphi(\frac{n}{d}) \]

注意到我们将环变成序列后唯一丢失的信息是开头结尾接起来的那个 \(1\) 段长度。

不妨直接枚举这个长度 \(t\),则对于开头,可以选择 \(t+1\) 个位置(\(t\)\(1\) 和最后的 \(0\) 作为开头),这些环本质都是一样的。剩下的就是开头结尾强制为 \(0\) 的方案数了。

定义 \(g(n,m)\) 为长为 \(n\) 的序列,开头结尾强制为 \(0\),放入 \(m\)\(1\),不存在 \(k+1\)\(1\) 段的方案数。

由上,显然有:

  • \(m\le k,f(n,m)={n\choose m}\)
  • \(m>k,f(n,m)=\sum_{t=0}^k(t+1)g(n-t,m-t)\)

我们考虑计算 \(g\),其实这本质上是把 \(m\)\(1\) 分为 \(n-m-1\) 组,每一组的前后都有一个 \(0\)

这等价于 \(x_1+x_2+…+x_{n-m-1}=m,\forall i,x_i\in [0,k]\cap \mathbb{Z}\) 的解的个数。

其实可以用插板法转化为:

定义 \(h(n,m)\) 为将 \(n\)\(1\) 分为 \(m\) 个段,每个段长度在 \([0,k]\) 的方案数。

\(g(n,m)=h(m,n-m-1)\)

现在考虑求解 \(h(n,m)\)

这是一个经典的背包问题,有 \(f_{i,j}\to f_{i+1,j+x},x\in [0,k]\)

但是这显然太慢了,我们需要更快的解决方案。

其实这同样是一个经典的二项式反演问题。

\(F_i\) 为钦定有 \(i\) 个数超过 \(k\) 的方案数,\(G_i\) 为恰好有 \(i\) 个数超过 \(k\) 的方案数。

显然有:

\[F_i={n-(k+1)i+m-1\choose m-1} \]

\[G_0=\sum^{i\le m,(k+1)i\le n} F_i(-1)^i{m\choose i} \]

至此问题解决,复杂度 \(O(\sum_{d|n}d)=O(n\log \log n)\)

code
/*
不错的题目,下面简单梳理一下思路

首先利用Burnside引理转化
n*ans=sum_{d|n} phi(n/d)[m%(n/d)==0]*f(d,m/(n/d))
f(n,m):长为n,用m个1与n-m个0,使得环形下没有长为k+1的1段的01序列个数,只考虑[1,n]不同则方案不同
枚举接到环的1长度t,t\in [0,k],有t+1个可能的起点
然后f(n,m)=sum_{t\in[0,k]}(t+1)*g(n-t,m-t)
g(n,m):序列上,长度n,开头结尾都是0,用m个1,不存在k+1的1段的方案数
这等价于n-m个0,钦定开头结尾
这等价于
x_1+x_2+……+x_{n-m-1}=m,且x_i in [0,k] 方案数,这东西可以用生成函数算,但是就带2log了,估计会T
设
h(n,m):n个1,m个段,则h(n,m)=g(m,n-m-1)
可以二项式反演
设F(i)为至少有i个x>k的方案数,G(i)为恰好
F(i)=C(n-(k+1)*i+m-1,m-1),G(0)=\sum_{i [0,n]}F(i)*(-1)^i*C(m,i)

*/
#include<bits/stdc++.h>
using namespace std;
#define N 1050500
#define int long long 
const int p=998244353;
const int up=2e5+5;
int jc[N],inv[N],n,m,tot,phi[N],v[N],k;
int power(int a,int b){
    int res=1;
    while(b){
        if(b&1)res=res*a%p;
        a=a*a%p;b>>=1;
    }
    return res;
}
int C(int n,int m){
    if(n<0||m<0||n<m)return 0;
    return jc[n]*inv[m]%p*inv[n-m]%p;
}
void init(){
    jc[0]=1;
    for(int i=1;i<=up;i++)jc[i]=jc[i-1]*i%p;
    inv[up]=power(jc[up],p-2);
    for(int i=up;i;--i)inv[i-1]=inv[i]*i%p;
    for(int i=1;i<=up;i++)phi[i]=i;
    for(int i=2;i<=up;i++){
        if(!v[i]){
            phi[i]=i-1;
            for(int j=i+i;j<=up;j+=i){
                phi[j]*=phi[i];
                phi[j]/=i;v[j]=1;
            }
        }
    }
}
int h(int n,int m){
    int w=1,res=0;
    for(int i=0;i*(k+1)<=n&&i<=m;i++)res+=w*C(m,i)%p*C(n-(k+1)*i+m-1,m-1)%p,w*=-1;
    return (res%p+p)%p;
}
int g(int n,int m){
    return h(m,n-m-1);
}
int f(int n,int m){//m!=n
    if(m<=k)return C(n,m);//简称随便放
    int res=0;
    for(int i=0;i<=k;i++)res=(res+(i+1)*g(n-i,m-i)%p)%p;
    return res;
}
/*
f(n,m):长为n,用m个1与n-m个0,使得环形下没有长为k+1的1段的01序列个数,只考虑[1,n]不同则方案不同
枚举接到环的1长度t,t\in [0,k],有t+1个可能的起点
然后f(n,m)=sum_{t\in[0,k]}(t+1)*g(n-t,m-t)
*/
int calc(){
    int ans=0;
    for(int d=1;d<=n;d++){
        if(n%d==0&&m%(n/d)==0){
            (ans+=phi[n/d]*f(d,m/(n/d))%p)%=p;
        }
    }
    ans=ans*power(n,p-2)%p;
    return ans;
}
signed main(){
    cin>>n>>m>>k;
    init();
    int res=calc();
    res=(res%p+p)%p;
    cout<<res<<"\n";
}

[SHOI2006] 有色图

\(n\) 个点的完全图中,用 \(m\) 种颜色给每条边上色后的本质不同图个数。两个图本质不同当且仅当不存在一种点的置换 \(f\),满足置换后两张图相同。

在《组合数学》一书中,Polya 定理的最后一个位置即为解决 \(n=4,m=2\) 的情形。

这启发我们探究 点置换边置换 的关系

显然存在 \(n!\) 个置换,且感性理解点边置换一一对应。

而对于一个长为 \(len\) 的点置换,例如:\({1,2,3,4}\choose {2,3,4,1}\)

其对应边置换 \({1,2,3,4,5,6\choose 4,5,1,6,2,3}\),这里按照 \((1,2),(1,3)……\) 进行编号。

又如 \({1,2,3\choose 2,3,1}\implies {1,2,3\choose 2,3,1}\)

容易发现一个大小为 \(n\) 的点循环对应形成边的循环,恰好 \(\lfloor \frac{n}{2}\rfloor\) 个。证明是容易的。

然后考虑两个不同轮换里的点所形成的边。设两个轮换大小为 \(p,q\)

则显然 \(lcm(p,q)\) 次轮换后这条边轮回来,而对于所有的边这都是一样的,且感性这循环必然封闭,因为点集封闭。

因此总共有 \(\frac{pq}{lcm(p,q)}=\gcd(p,q)\) 个循环,这和证明 \(Burnside\) 有点像。

那对于点循环大小分别为 \(i_1,i_2……i_m\) 对应边循环个数 \(\sum_{a<b}\gcd(i_a,i_b)+\sum_{a}\lfloor{\frac{i_a}{2}}\rfloor\) 个循环。

对于一组 \((i_1,i_2,……,i_m)\),设 \(c_x\)\(\sum [i_a=x]\) ,则显然有 \({n\choose {i_1,i_2……i_m}}\times \frac{\prod_{a\in [1,m]}(i_a-1)!}{\prod_{a\in [1,n]} c_a}=\frac{n!}{(\prod i_a)(\prod c_i!)}\) 个排列对应这个置换。

这是简单的,首先划分点属于哪个置换,这是多项式系数,然后乘圆排列内部,然后再对同样大小的等价置换去重。

根据 \(Burnside\) 引理,\(n!\) 正好消去,答案为:

\[\sum_{i_1\le i_2\le……\le i_m,\sum i_a=n}\frac{m^{\sum_{a<b}\gcd(i_a,i_b)+\sum_{a}\lfloor{\frac{i_a}{2}}\rfloor}}{(\prod i_a)(\prod c_i!)} \]

DFS 爆搜即可。

code
/*

组合数学里给出了 $n=4$ 的情况。现在来解决一般情况。
显然存在 $n!$ 个置换,且感性理解点边置换一一对应。
而对于一个长为 $len$ 的点置换,例如:${1,2,3,4}\choose {2,3,4,1}$
其对应边置换 ${1,2,3,4,5,6\choose 4,5,1,6,2,3}$,这里按照 $(1,2),(1,3)……$ 进行编号。
又如 ${1,2,3\choose 2,3,1}\implies {1,2,3\choose 2,3,1}$
打表发现每个点的循环置换对应形成边的循环,且恰好 $\lfloor \frac{n}{2}\rfloor$ 个。
然后考虑两个不同轮换里的点所形成的边。设两个轮换大小为 $p,q$
则显然 $lcm(p,q)$ 次轮换后这条边轮回来,而对于所有的边这都是一样的,且感性这循环必然封闭,因为点集封闭。
因此总共有 $\frac{pq}{lcm(p,q)}=\gcd(p,q)$ 个循环,这和证明 $Burnside$ 有点像。
那对于点循环大小分别为 $i_1,i_2……i_m$ 对应边循环个数 $\sum_{a<b}\gcd(i_a,i_b)+\sum_{a}\lfloor{\frac{i_a}{2}}\rfloor$ 个循环。
对于一组 $(i_1,i_2,……,i_m)$,设 $c_x$ 为 $\sum [i_a=x]$ ,则显然有 ${n\choose {i_1,i_2……i_m}}\times \frac{\prod_{a\in [1,m]}(i_a-1)!}{\prod_{a\in [1,n]} c_a}=\frac{n!}{(\prod i_a)(\prod c_i!)}$ 个排列对应这个置换。
这是简单的,首先划分点属于哪个置换,这是多项式系数,然后乘圆排列内部,然后再对同样大小的等价置换去重。

根据 $Burnside$ 引理,$n!$ 正好消去,答案为:

$$\sum_{i_1\le i_2\le……\le i_m,\sum i_a=n}\frac{m^{\sum_{a<b}\gcd(i_a,i_b)+\sum_{a}\lfloor{\frac{i_a}{2}}\rfloor}}{(\prod i_a)(\prod c_i!)}$$


*/
#include<bits/stdc++.h>
using namespace std;
#define N 1050505
#define int long long 
int n,m,p,ans;
int pw[N],jc[N],inv[N],gd[505][505],inv_v[N];
int power(int a,int b){
    int res=1;
    while(b){
        if(b&1)res=res*a%p;a=a*a%p;b>>=1;
    }
    return res;
}
int c[N],a[N];
void dfs(int now,int sur,int lst){
    if(!sur){
        int res=0;
        // for(int i=1;i<now;i++)cout<<a[i]<<" ";cout<<"\n";
        for(int i=1;i<now;i++)res+=a[i]/2;
        for(int i=1;i<now;i++)for(int j=i+1;j<now;j++)res+=gd[a[i]][a[j]];
        res%=(p-1);
        res=pw[res];
        // cout<<" "<<res<<"\n";
        for(int i=1;i<now;i++)res=res*inv_v[a[i]]%p;
        // cout<<" "<<res<<"\n";
        for(int i=1;i<=n;i++)res=res*inv[c[i]]%p;
        ans+=res;ans%=p;
        return ;
    }
    for(int i=1;i<=min(lst,sur);i++){
        c[i]++;a[now]=i;
        dfs(now+1,sur-i,i);
        c[i]--;a[now]=0;
    }
}
int gcd(int a,int b){
    return !b?a:gcd(b,a%b);
}
void init(){
    int up=min(p-1,1000000ll);
    jc[0]=1;pw[0]=1;
    for(int i=0;i<=500;i++)for(int j=0;j<=500;j++)gd[i][j]=gcd(i,j);
    for(int i=1;i<=up;i++)pw[i]=pw[i-1]*m%p,jc[i]=jc[i-1]*i%p;
    inv[up]=power(jc[up],p-2);
    for(int i=up;i;--i)inv[i-1]=inv[i]*i%p;
    for(int i=1;i<=up;i++)inv_v[i]=jc[i-1]*inv[i]%p;
}
signed main(){
    cin>>n>>m>>p;
    init();dfs(1,n,n);ans=(ans%p+p)%p;
    cout<<ans<<"\n";
}

组合符号化-CYC构造

组合对象 \(A\) 生成所有循环不同构的组合。记作 \(Log(A)\),我们先利用 \(Burnside\) 求解大小为 \(n\) 的项,循环位移 \(d\) 产生大小为 \(\gcd(n,d)\),并将其复制 \(\frac{n}{\gcd(n,d)}\) 次。

\[\begin{aligned} Log(A)[z^n]&=\frac{1}{n}\sum_{d=1}^nA(z^\frac{n}{\gcd(d,n)})^{\gcd(d,n)}\\ &=\frac{1}{n}\sum_{d|n}\varphi(\frac{n}{d})A(z^{\frac{n}{d}})^d\\ Log(A)(z)&=\sum_{n=1}\sum_{d|n}\varphi(\frac{n}{d})A(z^{\frac{n}{d}})^d\\ &=\sum_{d=1}\sum_{d|n}\frac{1}{n}\varphi(\frac{n}{d})A(z^{\frac{n}{d}})^d\\ &=\sum_{d=1}\sum_{t=1}\frac{1}{td}\varphi(d)A(z^d)^t\\ &=\sum_{d=1}\frac{\varphi(d)}{d}\sum_{t=1}\frac{1}{t}A(z^t)\\ &=\sum_{d=1}\frac{\varphi(d)}{d}\ln\frac{1}{1-A(z^d)}\\ &=\sum_{n=1}\frac{\varphi(n)}{n}\ln\frac{1}{1-A(z^n)} \end{aligned} \]

计算方法:先算出 \(\ln \frac{1}{1-A(z)}\),利用调和级数求和算出各项系数。

模型总结

  • 环形计数:

    \(\mathbb{G}\) 为循环置换群时,可以发现 \(f^i\)\(f^0\) 的循环节长度是 \(\gcd(i,n)\),设 \(F(x)\) 为链状长为 \(x\) 的答案,因此有:

    \(N(\mathbb{G},\mathbb{C})=\frac{1}{|\mathbb{G}|}\sum_{i=1}^nF(\gcd(i,n))=\sum_{d|n}\varphi(\frac{n}{d})F(d)\)

  • 点置换与边置换 \(\sum \lfloor\frac{i_j}{2}\rfloor+\sum_{a<b}\gcd(i_a,i_b)\)

  • 难点其实在于转化之后,而本质不同,循环置换的计数需要自然联想到Burnside 与 Polya

参考资料:

posted @ 2024-05-14 01:05  spdarkle  阅读(5)  评论(0)    收藏  举报