Loading [MathJax]/jax/output/CommonHTML/jax.js

浅谈群论

一些基础

子群

HG的子集且<H,op>为群,则<H,op><G,op>的子群

H既满足封闭性且求逆封闭,a,bH,abH,a1H

等价于a,bH,ab1H

一些特殊特殊的子群:

生成子群:aG,则<{ai,iZ},op>称为生成子群

正规化子:aG,则<{x|ax=xa,xG>称为正规化子,记为N(a)

共轭子群:aG,HG的子群,则xHx1称为H的共轭子群

等价类

等价关系:满足自反性a=a,对称性a=bb=a,传递性a=b,b=ca=c(=代表的是等价关系)

等价类:x的等价类[x]R={y|<x,y>∈R},R是满足某种等价关系两个元素所有集合

可以认为是把等价关系看作边,[x]Rx所在联通块的大小

商集:[A/R]指在以R为等价关系时等价类的集合

陪集

陪集分为右陪集与左陪集,两个没区别

对于aG,HG的子群,称Ha={ha|hH}H的右陪集

如果H为有限集,则|Ha|=|H|(不会证)

Lagrange定理

HG的子群,则|G||H|的倍数

考虑用陪集分解群

首先有个结论,a,bG,HG的子群,aHbHa=Hbab1H

若已知aHb,则a=h1b,h1H,h2H,h2a=h2h1b,且h2h1H

HaHb,反过来同理的HbHa,即Ha=Hb

若已知Ha=Hb,则h1,h2H,h1a=h2b,ab1=h11h2H

若已知ab1H,则ab1=hH,则a=hbHb

如果将Ha=Hb视为一种等价关系,H一定单独是一个等价类

aH,则HaHe,即a一定不与e在同一等价类

|Ha|=|H|,所以所有等价类大小相同

|G||H|=|[G/R]|,R={<a,b>,Ha=Hb}

由此还可以得到共轭类分解

共轭关系也是一种等价关系,将aG,所有与a共轭的b形成的集合称为共轭类

a所在的共轭类大小为|G||N(a)|

x,yG,xax1=yay1

xa=yay1xy1xa=ay1xy1xN(a)xN(a)=yN(a)

如果沿用陪集分解的思路,因为xN(a)=yN(a)x,y属于同一个等价类

N(a)陪集分解,对于其中的一个等价类中所有的元素x,xax1确定a的一个共轭

则共轭类的大小即为N(a)陪集分解后的等价类个数

置换群相关定理

置换群

置换群即为一个n元排列P组成的集合,定义运算PG=(GPi)

可证满足封闭性与求逆封闭

如果将iPi连有向边,则图为若干个不相交的环(n条边n个点)

当然,有时置换群不一定是一个排列的集合,但一定是置换的集合

轨道-稳定子群定理

定义一个集合A,G为一个作用于A的置换群,aA

定义Ga={g|g(a)=a,gG},称为稳定子群

G(a)={g(a),gG},称为轨道

|G|=|G(a)|×|Ga|,证明如下

x,yG,且x(a)=y(a),则a=x1(y(a))x1yGaxGa=yGa

GGa陪集分解,则当x(a)=y(a)x,y属于同一等价类

考虑等价类的个数即为有多少个不同的x(a)即为|G(a)|

Burnside 引理

[A/G]=1|G|gG[Ag],Ag的定义与Ga类似,就是Ag={a|g(a)=a,aA}

|Ga|=|G||G(a)|,两边同时求和

aA|Ga|=aA|G||G(a)|=|G|aA1|G(a)|

观察aA1|G(a)|,本质为轨道个数(每一个a所在的等价类大小分之1求和就是等价类的个数)=[A/G]

aA|Ga|=gG[Ag]=|G|×|[A/G]|

[A/G]=1|G|gG[Ag]

在这里我们给问题赋予一个实际意义

考虑A表示问题的所有方案,G为问题视为重复方案的置换

[A/G]即为将G看作一个等价关系的集合后划分出的等价类集合

Ga即为满足对a置换作用后依旧不变的置换,Ag差不多

G(a)为与a一起视为一种方案的方案集合,也可一看作是a所在的等价类

再具体一点的例子就是环的着色问题

Pólya 定理

具体到染色问题,假设有m种颜色

Ag=mc(g),c(g)g的不相交循环个数

【模板】Pólya 定理

给定一个 n 个点,n 条边的环,有 n 种颜色,给每个顶点染色,问有多少种本质不同的染色方案,答案对 109+7 取模

注意本题的本质不同,定义为:只需要不能通过旋转与别的染色方案相同

很明显G为一个轮换了i次的置换群

问题在于计算c(g),考虑g是轮换了i次的的置换,当前位置为p

p>(p+i)mod n>(p+2i)mod n.....pmod n=p

p+(n/c(g))i=p+kn,即c(g)=ik,则c(g)既为n的因数也为i的因数且最大

c(g)=gcd(i,n)

[A/G]=1|G|gGnc(g)=1nni=1ngcd(i,n)

f(x)=nx

[A/G]=1nni=1f(gcd(i,n))=1nd|nf(d)i=1[gcd(i,n)=d]=1nd|nf(d)ϕ(nd)

这里用dfs凑因子可以做到Θ(n)

#include<bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
int t;
int n;
int Pow(int a,int b,int p)
{
	int res=1;
	int base=a;
	while(b)
	{
		if(b&1)
		{
			res=((long long)res*base)%p;
		}
		base=((long long)base*base)%p;
		b>>=1;
	} 
	return res;
}
vector<pair<int,int> >Rec;
int Phi[105][105];
int Pri[105][105];
int Used[105];
int Res=0;
void dfs(int x)
{
	if(x==Rec.size())
	{
		int d=1;
		int phi=1;
		for(int i=0;i<Rec.size();i++)
		{
			d=(d*Pri[i][Used[i]]);
			phi=(phi*Phi[i][Used[i]]);
		}
		Res=((long long)Res+((long long)phi*Pow(n,(n/d)-1,MOD))%MOD)%MOD;
		return;
	}
	int Lim=Rec[x].second;
	for(int i=0;i<=Lim;i++)
	{
		Used[x]=i;
		dfs(x+1);
	}
}
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		Rec.clear();
		scanf("%d",&n);
		Res=0;
		int now=n;
		for(int d=2;d*d<=now;d++)
		{
			if(now%d==0)
			{
				int Tot=0;
				while(now%d==0)
				{
					now/=d;
					Tot++; 
				}
				Rec.push_back(make_pair(d,Tot));
			}
		}
		if(now>1)
		{
			Rec.push_back(make_pair(now,1));
		}
		for(int i=0;i<Rec.size();i++)
		{
			int Lim=Rec[i].second;
			int p=Rec[i].first;
			Phi[i][0]=1;
			Pri[i][0]=1;
			for(int j=1;j<=Lim;j++)
			{
				Pri[i][j]=Pri[i][j-1]*p;
				Phi[i][j]=Pri[i][j]-Pri[i][j-1];
			}
		}
		dfs(0);
		printf("%d\n",Res);
	}
} 

Magic Bracelet

金妮的生日快到了。哈利波特正在为他的新女友准备生日礼物。礼物是一个由n颗魔法珠组成的魔法手镯。有m种不同的魔珠。每种珠子都有其独特的特征。将许多珠子串在一起,将制作一个漂亮的圆形魔法手镯。正如哈利波特的朋友赫敏所指出的那样,某些种类的珠子会相互作用并爆炸,哈利波特必须非常小心地确保这些对的珠子不会并排串在一起,有无数种珠子。如果忽略围绕手镯中心旋转产生的重复,哈利能制作多少种不同的手镯?找到取模 9973 的答案。

同样定义G为轮换i次的置换群,但由于不能随便染色,所以不能用Pólya定理

[A/G]=1|G|gG|Ag|

瓶颈在于计算|Ag|

我们将g拆分成不同的循环,这些循环的内部的点颜色是相同的且每个循环大小相同,问题是不同循环之间的关系

如果我们把一个循环看成一个点,再将和他有关系的连边,最后连出还是一个环

我们可以考虑只在这个环上计算答案

f(x)为长度为x的环时的答案

[A/G]=1ngG|Ag|=1nd|nf(d)ϕ(nd)

现在问题在与如何计算f(x)

构造一个邻接矩阵T,矛盾为0,否则为1,则Tx时的对角线之和即为f(x)

#include<cstdio>
#include<vector>
#include<utility>
#include<cstring>
using namespace std;
const int MOD=9973;

int t;
int m;
int x,y;
int k;
int Pow(int a,int b,int p)
{
	int res=1;
	int base=(a%p);
	while(b)
	{
		if(b&1)
		{
			res=(res*base)%p;
		}
		base=(base*base)%p;
		b>>=1;
	} 
	return res;
}
struct Martix{
    int n, m;
    int val[10][10];
    void clear() { memset(val, 0, sizeof(val)); }
    void init() {
        clear();
        for (int i = 0; i < n; i++) {
            val[i][i] = 1;
        }
    }
    Martix operator*(const Martix x) const {
        Martix Res;
        Res.n = n;
        Res.m = x.m;
        Res.clear();
        for (int k = 0; k <m; k++) {
            for (int i = 0; i < Res.n; i++) {
                for (int j = 0; j < Res.m; j++) {
                    Res.val[i][j]=(Res.val[i][j]+val[i][k]*x.val[k][j])%MOD;
                }
            }
        }
        return Res;
    }
}A;
Martix ppow(Martix Ad, int b) {
    Martix Res;
    Res=Ad;
    Res.init();
    Martix Base = Ad;
    while (b) {
        if (b & 1) {
            Res = Res * Base;
        }
        Base = (Base * Base);
        b >>= 1;
    }
    return Res;
}
int F(int x)
{
	Martix IDSY=ppow(A,x);
	int Res=0;
	for(int i=0;i<m;i++)
	{
		Res=(Res+IDSY.val[i][i])%MOD;
	}
	return Res;
}
vector<pair<int,int> >Rec;
int Phi[205][205];
int Pri[205][205];
int Used[205];
int Res=0;
int n;
void dfs(int x)
{
	if(x==Rec.size())
	{
		int d=1;
		int phi=1;
		for(int i=0;i<Rec.size();i++)
		{
			d=(d*Pri[i][Used[i]]);
			phi=(phi*Phi[i][Used[i]])%MOD;
		}
		Res=(Res+((phi)%MOD*F((n/d)))%MOD)%MOD;
		return;
	}
	int Lim=Rec[x].second;
	for(int i=0;i<=Lim;i++)
	{
		Used[x]=i;
		dfs(x+1);
	}
}

int main()
{
	scanf("%d",&t);
	while(t--)
	{
		Rec.clear();
		scanf("%d %d %d",&n,&m,&k);
		A.clear();
		A.n=m;
		A.m=m;
		for(int i=1;i<=A.n;i++)
		{
			for(int j=1;j<=A.n;j++)
			{
				A.val[i-1][j-1]=1;
			}
		}
		for(int i=1;i<=k;i++)
		{
			scanf("%d %d",&x,&y);
			A.val[x-1][y-1]=0;
			A.val[y-1][x-1]=0;
		}
		Res=0;
		int now=n;
		for(int d=2;d*d<=now;d++)
		{
			if(now%d==0)
			{
				int Tot=0;
				while(now%d==0)
				{
					now/=d;
					Tot++; 
				}
				Rec.push_back(make_pair(d,Tot));
			}
		}
		if(now>1)
		{
			Rec.push_back(make_pair(now,1));
		}
		for(int i=0;i<Rec.size();i++)
		{
			int Lim=Rec[i].second;
			int p=Rec[i].first;
			Phi[i][0]=1;
			Pri[i][0]=1;
			for(int j=1;j<=Lim;j++)
			{
				Pri[i][j]=Pri[i][j-1]*p;
				Phi[i][j]=Pri[i][j]-Pri[i][j-1];
			}
		}
		dfs(0);
		Res=(Res*Pow(n,MOD-2,MOD))%MOD;
		printf("%d\n",Res);
	}
	return 0;
} 

[MtOI2018]魔力环

wkr 希望能够得到一个由 n 个魔力珠串成的环。不过他对普通的环并不感兴趣,因此他提出了如下的要求:

  • wkr 希望在这个环上,恰好m 个黑色的魔力珠与 nm 个白色的魔力珠。
  • 由于 wkr 认为黑色魔力珠不应过于密集,因此 wkr 希望这个环上不会出现一段连续的黑色魔力珠,其长度超过 k

在 wkr 的心目中,满足上述要求的环才是美妙的。

不过这样的环可能并不唯一。 wkr 想要知道共有多少种不同的环满足他所提出的要求。然而 wkr 并不喜欢计算,他希望聪明的你能够告诉他答案。

在这里,我们认为两个环是不同的,当且仅当其中一个环仅通过旋转无法得到另一个环

由于答案可能过大,因此输出答案对 998,244,353 取模后的结果。

沿用上一题的思路

[A/G]=1ngG|Ag|=1nd|nf(d)ϕ(nd),f(d)为长度为d时的答案,此时黑色点的数量为cb=(md(n)),这就要求cbnd的倍数,cw=xcb为白色点的数目

考虑先构造一个点数为cw的环,然后考虑向里面插入cb个点且满足每个空隙的黑点数量不超过k

计数带标号,所以断环为链,枚举断点的黑点数量i

然后考虑剩下的链实际就是cw1个盒子,cbi个球,每个盒子不能超过k个球的方案

F(d)=min(k,cb)i=0(i+1)nj=0(cw1j)(cw1+cbi1kjcw11)

#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353;
const int MAXN=1e5+5;
int n;
int m;
int k;
int Pow(int a,int b,int p)
{
	int res=1;
	int base=(a%p);
	while(b)
	{
		if(b&1)
		{
			res=((long long)res*base)%p;
		}
		base=((long long)base*base)%p;
		b>>=1;
	} 
	return res;
}
int inv(int a,int p)
{
	return Pow(a,p-2,p);
}
int gcd(int a,int b)
{
	if(b==0)
	{
		return a;
	}
	return gcd(b,a%b);
}
int fac[MAXN];
int inv_fac[MAXN];
int C(int n, int m) {
	if(m<0||m>n)
	{
		return 0;
	}
    if (m == 0 || n == m)
        return 1;
    int k = fac[n];
    int ans = ((long long)k*inv_fac[n - m])%MOD;
    ans = ((long long)ans*inv_fac[m])%MOD;
    return ans;
}
int Cal(int n,int m,int k)
{
	int Rex=0;
	if(m<0)
	{
		return 0;
	}
	for(int i=0;i<=n;i++)
	{
		if(m<(k+1)*i)
		{
			break;
		}
		int Con=((long long)C(n,i)*C(n+m-1-(k+1)*i,n-1))%MOD;
		if(i&1)
		{
			Rex=((long long)Rex-Con+MOD)%MOD; 
		 } 
		 else
		 {
		 	Rex=((long long)Rex+Con)%MOD;
		 }
		 //printf("%d %d %d %d?\n",Con,i,n+m-1-(k+1)*i,n-1);
	}
	return Rex;
}
int F(int x)
{
	if(m%(n/x))
	{
		return 0;
	 } 
	 int N=x;
	int cb=(m/(n/x));
 	int cw=(x-cb);
 	if(cb<=k)
	 {
	 	return C(N,cb);
	 }
	int Res=0;
	for(int i=0;i<=min(cb,k);i++)
	{
		Res=((long long)Res+((long long)(i+1)*Cal(cw-1,cb-i,k))%MOD)%MOD;	
	}
	return Res;
}
vector<pair<int,int> >Rec;
int Phi[205][205];
int Pri[205][205];
int Used[205];
int Res=0;
void dfs(int x)
{
	if(x==Rec.size())
	{
		int d=1;
		int phi=1;
		for(int i=0;i<Rec.size();i++)
		{
			d=(d*Pri[i][Used[i]]);
			phi=(phi*Phi[i][Used[i]])%MOD;
		}
		Res=((long long)Res+((long long)(phi)*F(n/d))%MOD)%MOD;
		return;
	}
	int Lim=Rec[x].second;
	for(int i=0;i<=Lim;i++)
	{
		Used[x]=i;
		dfs(x+1);
	}
}

signed main()
{
	fac[0] = 1;
    for (int i = 1; i <= MAXN-5; i++) {
        fac[i] = ((long long)fac[i - 1] * i)%MOD;
    }
    inv_fac[MAXN-5] = inv(fac[MAXN-5], MOD);
    for (int i = MAXN-5 - 1; i >= 0; i--) {
        inv_fac[i] = ((long long)inv_fac[i + 1] * (i + 1)) % MOD;
    }
	Rec.clear();
	scanf("%d %d %d",&n,&m,&k);
	Res=0;
	int now=n;
	for(int d=2;d*d<=now;d++)
	{
		if(now%d==0)
		{
			int Tot=0;
			while(now%d==0)
			{
				now/=d;
				Tot++; 
			}
			Rec.push_back(make_pair(d,Tot));
		}
	}
	if(now>1)
	{
		Rec.push_back(make_pair(now,1));
	}
	for(int i=0;i<Rec.size();i++)
	{
		int Lim=Rec[i].second;
		int p=Rec[i].first;
		Phi[i][0]=1;
		Pri[i][0]=1;
		for(int j=1;j<=Lim;j++)
		{
			Pri[i][j]=Pri[i][j-1]*p;
			Phi[i][j]=Pri[i][j]-Pri[i][j-1];
		}
	}
	dfs(0);
	Res=((long long)Res*inv(n,MOD))%MOD;
	printf("%d\n",Res);
	return 0;
} 

[SHOI2006] 有色图

如果一张无向完全图(完全图就是任意两个不同的顶点之间有且仅有一条边相连)的每条边都被染成了一种颜色,我们就称这种图为有色图。如果两张有色图有相同数量的顶点,而且经过某种顶点编号的重排,能够使得两张图对应的边的颜色是一样的,我们就称这两张有色图是同构的。以下两张图就是同构的,因为假如你把第一张图的顶点 (1,2,3,4) 置换成第二张图的 (4,3,2,1),就会发现它们是一样的。

你的任务是,对于计算所有顶点数为 n,颜色种类不超过 m 的图,最多有几张是两两不同构的图。由于最后的答案会很大,你只要输出结论模 p 的余数就可以了(p 是一个质数)。

这里图的置换群G就是一个全排列,同样用Burnside

[A/G]=1|G|gG|Ag|,瓶颈还是在|Ag|,注意|Ag|是不动的边集

还是给g分解成几个循环

如果(u,v)是在同一个循环S

则一共有|S|2种边的循环(考虑一个正|S|多边形按边所对应的角度分类)

如果(u,v)不在同一个循环,分别在S1,S2

(u,v)会经过lcm(|S1|,|S2|)次转动后复原,也就是说(u,v)所在的边集环大小lcm(|S1|,|S2|)

个数则为|S1|×|S2|lcm(|S1|,|S2|)=gcd(|S1|,|S2|)

g的第i个轮换大小为bi

因而[A/G]=1|G|gG|Ag|=1n!gGmbi2+ij>igcd(i,j)

如果我们分拆n得到b,定义f(b)为轮换序列为bg的个数

[A/G]=1n!bmbi2+ij>igcd(i,j)f(b)

考虑f(b)的计算

将带标号的排列分成大小为bi几组n!bi!,再考虑组内顺序为bi!bi

同时相同的bi是无顺序的,还要乘上1vbi!

f(b)=n!bivbi!

#include<bits/stdc++.h>
using namespace std;
int n,m,MOD;
int fac[60];
int inv_fac[60];
int Inv[60];
int Pow(int a,int b,int p)
{
	int res=1;
	int base=(a%p);
	while(b)
	{
		if(b&1)
		{
			res=((long long)res*base)%p;
		}
		base=((long long)base*base)%p;
		b>>=1;
	} 
	return res;
}
int inv(int a,int p)
{
	return Pow(a,p-2,p);
}
int gcd(int a,int b)
{
	if(b==0)
	{
		return a;
	}
	return gcd(b,a%b);
}
int b[60];
int v[60];
int Res=0;
int Gcd[65][65];
void dfs(int x,int Rest,int las)
{
	if(Rest==0)
	{
		for(int i=1;i<=n;i++)
		{
			v[i]=0;
		}
		int Tc=0;	
		for(int i=1;i<=x;i++)
		{
			v[b[i]]++;
			Tc=((long long)Tc+(b[i]/2))%(MOD-1); 
		}
		for(int i=1;i<=x;i++)
		{
			for(int j=i+1;j<=x;j++)
			{
				Tc=((long long)Tc+Gcd[b[i]][b[j]])%(MOD-1);
			}
		}
		int Con=Pow(m,Tc,MOD);
		for(int i=1;i<=x;i++)
		{
			Con=((long long)Con*Inv[b[i]])%MOD;
		}
		for(int i=1;i<=n;i++)
		{
			Con=((long long)Con*inv_fac[v[i]])%MOD;
		}
		Res=((long long)Res+Con)%MOD;
		return;
	}
	for(int i=las;i<=Rest;i++)
	{
		b[x+1]=i;
		dfs(x+1,Rest-i,i);
	}
}

int main()
{
	scanf("%d %d %d",&n,&m,&MOD);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			Gcd[i][j]=gcd(i,j); 
		}
		Inv[i]=inv(i,MOD);
	}
	
	fac[0] = 1;
    for (int i = 1; i <= n; i++) {
        fac[i] = ((long long)fac[i - 1] * i)%MOD;
    }
    inv_fac[n] = inv(fac[n], MOD);
    for (int i = n - 1; i >= 0; i--) {
        inv_fac[i] = ((long long)inv_fac[i + 1] * (i + 1)) % MOD;
    }
	dfs(0,n,1); 
	printf("%d\n",Res);
} 
posted @   kid_magic  阅读(243)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示