「专题总结」群论

万事先吐槽:为什么我在这个专题疯狂被卡常啊

群论这玩意是真的不接地气。刚开始听的时候这是个什么玩意啥也听不懂啊。。。

然而其实有几个概念,显得很高端而已。(下面开始抄理解深刻的(他自己说的)$yxs$的博客

所谓的置换,其实就是把元素换位置。

置换群$G$就是一堆置换,满足存在逆元和单位元(不动呗),有结合律,封闭性。。。

不动点就是某一个置换$i$中有多少个元素位置并没有改变,称为$c_i$。

$k$不动置换类就是所有使$k$位置元素位置不变的置换的集合,称为$Z_k$

等价类就是元素$k$在任意置换后可能出现的位置集合,称为$E_k$

根据含义可知等价类之间没有交集,它们的并集是全集。等价类的数目称为$L$

然后就有了烧边$Burnside$引理:$L=frac{sum c_i}{|G|}$。就是不动点的平均值。

然而我不会也不想证明。。。这个东西没什么扩展性,记结论差不多就够了

如果哪天需要了,被打脸了就上去看群论之神的博客就好了。

以及还有$Burnside$的具体应用$Polya$。如果没有元素个数等限制的话:

$L=frac{sum m^{h_i}}{|G|}$

其中$m$是可以染的颜色种数,$h_i$表示元素$i$所在的循环节个数,其实也就是总置换数除以循环节长度。

然后记住结论就可以做题了。

因为理解很不深刻所以做这些题非常艰难,然后还颓了不少题解。。。在$yxm$说是水水水的题目上。

只能说,和$CSP-S 390+$的所有大佬们显然还是有双重巨大的差距啊。。。

题目顺序按照知识点的深浅排序,也是推荐的做题顺序。大致是难度排序。

(如果做题顺序反了的话,基本就要想我一样一路颓题解下来了)

Cards:

$Description:$

小春现在很清闲,面对书桌上的N张牌,他决定给每张染色,目前小春只有3种颜色:红色,蓝色,绿色.他询问Sun有多少种染色方案,Sun很快就给出了答案.进一步,小春要求染出Sr张红色,Sb张蓝色,Sg张绝色.他又询问有多少种方案,Sun想了一下,又给出了正确答案. 最后小春发明了M种不同的洗牌法,这里他又问Sun有多少种不同的染色方案.两种染色方法相同当且仅当其中一种可以通过任意的洗牌法(即可以使用多种洗牌法,而每种方法可以使用多次)洗成另一种.Sun发现这个问题有点难度,决定交给你,答案可能很大,只要求出答案除以P的余数(P为质数).$Max{Sr,Sb,Sg}<=20,m<=60,m+1<p<100,n=Sr+Sb+Sg$

输入数据保证任意多次洗牌都可用这 m种洗牌法中的一种代替,且对每种洗牌法,都存在一种洗牌法使得能回到原状态。

其实我觉得题还不错,再看一遍又不会了。。。我还是有必要讲一讲的。

因为对总数有限制,所以不能$Polya$,所以选择去烧个边。

首先,题目中的洗牌法并不存在单位元,所以我们强制加入一种“洗牌法”是所有牌都不变。

数据范围很小,对于每种洗牌法我们都可以暴力算出循环节大小与个数。

对于每个循环节进行$dp$,每个循环节都要染成相同的颜色。

然后就可以统计答案了。其实有点像个二维背包。

别忘了烧边的时候最后答案要去掉置换群的大小,因为$p$是质数而且出题人仁慈的保证了$m+1<p$所以$exgcd$或快速幂乱写都行。

 1 #include<cstdio>
 2 #define n (r+b+g)
 3 int pp,q[61][61],r,b,g,m,p,al[61],cir[61],cnt_cir,dp[61][21][21],ans,x,y,siz[61];
 4 void ex_gcd(int a,int b,int &x,int &y){
 5     if(!b){x=1;y=0;return;}
 6     ex_gcd(b,a%b,x,y);
 7     int res=x;x=y;y=res-a/b*x;
 8 }
 9 int main(){
10     scanf("%d%d%d%d%d",&r,&b,&g,&m,&p);
11     for(int i=1;i<=m;++i) for(int j=1;j<=n;++j) scanf("%d",&q[i][j]);
12     for(int i=1;i<=n;++i) q[0][i]=i;
13     dp[0][0][0]=1;
14     for(int w=0;w<=m;++w){
15         cnt_cir=0;
16         for(int i=1;i<=n;++i)al[i]=0;
17         for(int i=1;i<=n;++i)if(!al[i]){
18             cnt_cir++; siz[cnt_cir]=1; pp=i; al[pp]=1;
19             while(!al[q[w][pp]]) al[q[w][pp]]=1, siz[cnt_cir]++, pp=q[w][pp];
20         }
21         for(int i=1;i<=cnt_cir;++i)for(int j=0;j<=r;++j)for(int k=0;k<=b;++k)dp[i][j][k]=0;
22         for(int i=1;i<=cnt_cir;++i)for(int j=r;j>=0;--j)for(int k=b;k>=0;--k){
23             (dp[i][j][k]+=dp[i-1][j][k])%=p;
24             if(j>=siz[i])(dp[i][j][k]+=dp[i-1][j-siz[i]][k])%=p;
25             if(k>=siz[i])(dp[i][j][k]+=dp[i-1][j][k-siz[i]])%=p;
26         }
27         (ans+=dp[cnt_cir][r][b])%=p;
28     }
29     ex_gcd(m+1,p,x,y);
30     x=(x%p+p)%p;
31     printf("%d
",ans*x%p);
32 }
View Code

poj2154Color

$Description:$

Beads of N colors are connected together into a circular necklace of N beads (N<=1000000000). Your job is to calculate how many different kinds of the necklace can be produced. You should know that the necklace might not use up all the N colors, and the repetitions that are produced by rotation around the center of the circular necklace are all neglected.

You only need to output the answer module a given number PX <= 3500,1 <= P <= 30000

旋转同构,翻转不同构的环染色。模数是$int$的根号基本就代表。。。挺卡常的

非常符合$Polya$的形式,问题就在于统计循环节个数和对应的置换个数了。

置换有哪些?单位元,转$frac{1}{n}$圈,转$frac{2}{n}$圈。。。转$frac{n-1}{n}$圈。

对于每个置换,循环节长度就是$frac{n}{gcd(i,n)}$。那么循环节个数就是$gcd(i,n)$。

但是你要做$n$次$gcd$?太慢 了,就算$O(1)$也不行啊。

$gcd$的值只可能是$n$的约数,这样的数只有$sqrt{n}$个,枚举它们。

然后以此为$gcd$的值有$varphi (n/i)$个。考虑欧拉函数的实际含义就可以理解。

欧拉函数只要根号硬筛就可以了。答案就是$frac{sumlimits_{i|n} varphi(n/i) imes n^{i}}{n}$

然而除法懒得做的话可以直接在快速幂时指数-1偷个懒,因为这题恰好颜色数等于置换数。

这也导致我在颓$std$的时候懵逼了半天。

因为卡常不能define int long long,求欧拉函数最开始是先乘后除炸$int$,$WA$了$5$发,菜死了。

 1 #include<cstdio>
 2 int mod,n;
 3 int pow(int b,int t,int a=1){for(;t;t>>=1,b=b*b%mod)if(t&1)a=a*b%mod;return a;}
 4 int phi(int x){
 5     int ans=x;
 6     for(int i=2;i*i<=x;++i)if(x%i==0){
 7         ans=ans/i*(i-1);
 8         while(x%i==0)x/=i;
 9     }return (x^1?ans/x*(x-1):ans)%mod;
10 }
11 signed main(){
12     int t,ans;scanf("%d",&t);
13     while(t--){
14         scanf("%d%d",&n,&mod);ans=0;
15         for(int i=1;i*i<=n;++i)if(n%i==0){
16             ans=(ans+pow(n%mod,n/i-1)*phi(i))%mod;
17             if(i*i^n)ans=(ans+pow(n%mod,i-1)*phi(n/i))%mod;
18         }
19         printf("%d
",ans);
20     }
21 }
View Code

poj2888Magic Bracelet:

$Description:$

Ginny’s birthday is coming soon. Harry Potter is preparing a birthday present for his new girlfriend. The present is a magic bracelet which consists of n magic beads. The are m kinds of different magic beads. Each kind of beads has its unique characteristic. Stringing many beads together a beautiful circular magic bracelet will be made. As Harry Potter’s friend Hermione has pointed out, beads of certain pairs of kinds will interact with each other and explode, Harry Potter must be very careful to make sure that beads of these pairs are not stringed next to each other.

There infinite beads of each kind. How many different bracelets can Harry make if repetitions produced by rotation around the center of the bracelet are neglected? Find the answer taken modulo 9973

$1 ≤ n ≤ 10^9, gcd(n, 9973) = 1, 1 ≤ m ≤ 10,1 ≤ k ≤ m(m - 1)- 2)$

与上一题不同的在于,这道题对相邻元素有限制了。

先不考虑它是一个环,如果就是普通这样的限制让你做一条链,不难想到简单$dp$

但是复杂度不对$n$太大了,但是发现$m$小的出奇,于是可以用矩阵快速幂优化。

现在考虑,链变成环了怎么办?其实要求就是第1个元素和第n个元素也要满足限制。

断环成链的话就变成了第$n$个元素和第$n+1$个元素满足限制,同时第$1$和第$n+1$个元素相同就可以了。

这样只要记录下第一个元素是什么,然后做一个长度为$n+1$的链$dp$(就是转移$n$次)就可行了。

这样你就统计出了合法的置换类有多少种,烧个边弄个欧拉函数,和上一题就一样了。

模数在$int$的根号内。。。还被卡常。。。poj真无良。

 1 #include<cstdio>
 2 #define mod 9973
 3 int pow(int b,int t,int a=1){b%=mod;for(;t;t>>=1,b=b*b%mod)if(t&1)a=a*b%mod;return a;}
 4 int A[11][11],B[11][11],R[11][11],C[11][11],m;
 5 int phi(int x){int ans=x;
 6     for(int i=2;i*i<=x;++i)if(x%i==0){ans=ans/i*(i-1);while(x%i==0)x/=i;}
 7     return (x==1?ans:ans/x*(x-1))%mod;
 8 }
 9 void mul(){
10     for(int i=1;i<=m;++i)for(int j=1;j<=m;++j)for(int k=1;k<=m;++k)C[i][j]=(C[i][j]+B[i][k]*B[k][j])%mod;
11     for(int i=1;i<=m;++i)for(int j=1;j<=m;++j)B[i][j]=C[i][j],C[i][j]=0;
12 }
13 void Mul(){
14     for(int i=1;i<=m;++i)for(int j=1;j<=m;++j)for(int k=1;k<=m;++k)C[i][j]=(C[i][j]+A[i][k]*B[k][j])%mod;
15     for(int i=1;i<=m;++i)for(int j=1;j<=m;++j)A[i][j]=C[i][j],C[i][j]=0;
16 }
17 int ask(int n){int ans=0;
18     for(int j=1;j<=m;++j)for(int k=1;k<=m;++k)B[j][k]=R[j][k];
19     for(int i=1;i<=m;++i)for(int j=1;j<=m;++j)A[i][j]=0;
20     for(int i=1;i<=m;++i)A[i][i]=1;
21     for(int t=n;t;t>>=1,mul())if(t&1)Mul();
22     for(int i=1;i<=m;++i)ans=(ans+A[i][i])%mod;
23     return ans;
24 }
25 int main(){
26     int T;scanf("%d",&T);while(T--){
27         int n,k,ans=0;scanf("%d%d%d",&n,&m,&k);
28         for(int i=1;i<=m;++i)for(int j=1;j<=m;++j)R[i][j]=1;
29         for(int i=1,x,y;i<=k;++i)scanf("%d%d",&x,&y),R[x][y]=R[y][x]=0;
30         for(int i=1;i*i<=n;++i)if(n%i==0){
31             ans=(ans+phi(i)*ask(n/i))%mod;
32             if(i*i!=n)ans=(ans+phi(n/i)*ask(i))%mod;
33         }printf("%d
",ans*pow(n,mod-2)%mod);
34     }
35 }
View Code

周末晚会:

$Description:$

Irena和Sirup正准备下个周末的Party。为这个Party,他们刚刚买了一个非常大的圆桌。他们想邀请每个人,但他们现在不知道如何分配座次。Irena说当有超过K个女孩座位相邻(即这些女孩的座位是连续的,中间没有男孩)的话,她们就会说一整晚的话而不和其他人聊天。 Sirup没有其他选择,只有同意她。然而,作为一名数学家,他很快地痴迷于所有可能方案。 题目说明: N个人围绕着圆桌坐着,其中一些是男孩,另一些是女孩。你的任务是找出所有合法的方案数,使得不超过K个女孩座位是连续的。 循环同构会被认为是同一种方案。对于100%的数据N,K < = 2000;mod 100000007

限制条件变了,不再是相邻,而是不能连续超过$k$。

发现这次$n,k$范围都不大,所以可以做$O(nk)$的$dp$

然后还要再容斥一下,把首尾接起来超过$k$的部分去掉。

只要强制限制头上有连续几个女生和1个男生,剩下的正常选就可以。

然后可以前缀和优化,但是最好不要。。。

不然就会像我一样被卡常,$T$它个$5$发才爽。。。

然后就又和上一道题一样了。

还有这道题的模数,它比平时少个0!少了一个0!!!出题人真是吃多了。。。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define mod 100000007
 4 int dp[2222][2222],sum[2222][2222],k,n;
 5 int pow(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;}
 6 int Mod(int x){return x>=mod?x-mod:x;}
 7 int phi(int x){int ans=x;
 8     for(int i=2;i*i<=x;++i)if(x%i==0){
 9         ans=ans/i*(i-1);
10         while(x%i==0)x/=i;
11     }return x^1?ans/x*(x-1):ans;
12 }
13 int cal(int x,int ans=0){
14     for(int i=0;i<=k;++i)ans=Mod(ans+dp[x][i]);
15     for(int i=1;i<=k;++i)ans=Mod(ans-Mod(sum[max(x-k-2+i,0)][i]-sum[max(0,x-k-2)][i]+mod)+mod);
16     if(x<=k&&k<n)ans--;
17     return ans;
18 }
19 int main(){dp[0][0]=1;
20     int T;scanf("%d",&T);while(T--){
21         int ans=0;scanf("%d%d",&n,&k);
22         for(int i=1;i<=n;++i)for(int j=0;j<=k&&j<=i+1;++j)dp[i][j]=0;
23         for(int i=0;i<n;++i){
24             for(int j=0;j<=k&&j<=i+1;++j)dp[i+1][0]=Mod(dp[i+1][0]+dp[i][j]);
25             for(int j=0;j<k&&j<=i+1;++j)dp[i+1][j+1]=Mod(dp[i+1][j+1]+dp[i][j]);
26         }
27         for(int i=0;i<n;++i)sum[0][i]=dp[0][i];
28         for(int i=0;i<n;++i)for(int j=1;j<n;++j)sum[j][i]=Mod(sum[j-1][i]+dp[j][i]);
29         for(int i=1;i*i<=n;++i)if(n%i==0){
30             ans=(ans+1ll*phi(i)*cal(n/i))%mod;
31             if(i*i!=n)ans=(ans+1ll*phi(n/i)*cal(i))%mod;
32         }
33         printf("%lld
",1ll*pow(n,mod-2)*ans%mod);
34     }
35 }
View Code

color 有色图:

$Description:$

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

你的任务是,对于计算所有顶点数为n,颜色种类不超过m的图,最多有几张是两两不同构的图。

由于最后的答案会很大,你只要输出结论模p的余数就可以了(p是一个质数)$1≤n≤53,1≤m≤1000,n<p≤10^9$

这是真大神题了。。。啃题解才能明白。。。

要考虑好,这道题的置换的元素是什么?并不是点,而是边。点是对于边而言的置换。

(然而并不是每个点是一个置换,因为图是有标号的,实际上每个点的排列就是一个置换)

所以置换的全种类有$53!$种。太大了。

我们先从其它方面入手。因为点是边的置换,所以有一些点可以构成循环。

然后我们再考虑所有边就只分为了两类:两个端点是否在同一个循环内。

如果它在两个循环内,循环的点数分别为$x$和$y$的话,那么循环节长度就是$lcm(x,y)$。这样的话所有点都回到原点了。

这类的元素数(也就是边数)一共有$xy$个。所以说这样产生的循环节个数就是$frac{xy}{lcm(x,y)}=gcd(x,y)$了。

(弱智了,总元素等于循环节长度乘循环节个数。原来写的不动点个数。感谢青君大神指出。下文同)

如果边的两段在同一个循环内,循环的点数为$x$的话,那么这样的边有$C_x^2$个。

循环节长度当然是$x$,所以循环节个数是$frac{C_x^2}{x}=frac{x-1}{2}$

然而这样并没有考虑到所有情况。因为边是双向的,所以,在点数为偶数时,边翻转后是一样的,比上面的情况多一个类似于翻转同构的循环形式。

所以这时候循环节个数为$frac{x}{2}$。

综上所述,设某个循环$i$中的点数为$p_i$,那么总循环节个数就是$sumlimits_{i=1}^{cnt} lfloor frac{p_i}{2} floor + sumlimits_{i=1}^{cnt} sumlimits_{j=i+1}^{cnt} gcd(p_i,p_j)$

现在,对于一个给定循环的局面,我们已经可以求出它的循环节个数了。

于是就可以利用$Polya$定理的形式,知道$m^{c}$就是总的等价类数目。

现在我们只需要求出所有局面的和。

总的局面肯定是$n!$个了,这$n$个点可以随意排列在循环中的位置。

但是你枚举每一种局面总数是$n!$自然不可接受。我们只枚举本质不同的就好了,所谓本质不同,就是至少有一个循环的大小不一致。

这样的状态数还是有点多,我们强制循环的大小有序,必须从小到大,这样就可以开始愉快的搜索了。

因为$n$并不大只有$53$(这数据范围看着不像搜索剪枝嘛?),它的正整数拆分方案数并不多,所以搜就完了。

最后一个问题就是,我们现在强制有序还本质不同,问题在于这种局面到底出现了多少次。

我们把点拍到序列上,对于所有点进行全排列,每个循环内部的排列顺序是循环同构的,所以要除掉$p_i$

即$1234$和$2341$一样,但是和$1324$就不一样。

而且,因为保证了循环有序,大小相同的循环其实被规定了先后顺序,所以对于所有$i$如果有$s_i$个循环的大小都是$i$那么方案数就要除掉$i!$

所以总的方案就是$frac{n!}{prod (s_i!) prod p_i}$

对于每种划分,答案与不动点个数相乘计入总数,最后还是别忘了除掉总的置换群大小$n!$。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define int long long
 4 int mod,n,m,fac[66],inv[66],ans,s[66],I[66];
 5 int pow(int b,int t,int a=1){for(;t;t>>=1,b=b*b%mod)if(t&1)a=a*b%mod;return a;}
 6 int gcd(int x,int y){return y?gcd(y,x%y):x;}
 7 void cal(int t){
 8     int tot=0,ctb;
 9     for(int i=1;i<=t;++i)tot+=s[i]>>1;
10     for(int i=1;i<=t;++i)for(int j=i+1;j<=t;++j)tot+=gcd(s[i],s[j]);
11     ctb=pow(m,tot)*fac[n]%mod;
12     for(int i=1;i<=t;++i)ctb=ctb*I[s[i]]%mod;
13     for(int i=1,l=1;i<=t+1;++i)if(s[i]!=s[i-1])ctb=ctb*inv[i-l]%mod,l=i;
14     ans=(ans+ctb)%mod;
15 }
16 void sch(int lft,int lst,int t){
17     if(!lft)s[t+1]=0,cal(t);
18     for(int i=lst;i<=lft;++i)s[t+1]=i,sch(lft-i,i,t+1);
19 }
20 main(){
21     scanf("%lld%lld%lld",&n,&m,&mod); fac[0]=inv[0]=1;
22     for(int i=1;i<=n;++i)inv[i]=pow(fac[i]=fac[i-1]*i%mod,mod-2),I[i]=pow(i,mod-2);
23     sch(n,1,0);cout<<ans*inv[n]%mod<<endl;
24 }
思维极度复杂,代码极端好写
原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/12116151.html