【学习笔记】Burnside 定理和 Polya 定理
基本公式:
公式:
Burnside 定理:
其中 \(M (g)\) 表示在置换 \(g\) 的作用下,不动点的数量,\(G\) 代表置换群,也可以理解为所有的置换构成的集合。
Polya 定理:
其中 \(c(g)\) 代表置换 \(g\) 的循环的个数
其实根据这俩公式就能发现
其实这是两个很神奇的公式,它们解决的问题就是类似于求多少种本质不同的方案的个数,这两者之间也是可以进行互换。
公式解释:
比如最经典的项链染色:
所谓的置换数就是旋转的个数也就是所谓的能进行操作让他变化的操作数
所谓的不动点:
就是指经过了某一次操作也就是置换他还是他
所谓的循环个数:
有多少组位置,经过了一次置换之后使得这些位置上的数还是这些位置原本的那些数,只不过最多位置有所变化。
经典例题:
有 \(m\) 种颜色的珠子,要用它搞出一个长度为 \(n\) 的项链,求本质不同的方案数。这里的本质不同是指通过旋转或对称无法相同。
旋转
旋转的话假设我们旋转了 \(k\) 个,那么就是说看不动点的数量:因为不动点的数量和循环个数可以互换。假设旋转了 \(k\) 要使得整个项链还是它,那么就意味着整个序列存在一个最长为 \(gcd(n,k)\) 的循环节,那么就考虑这样一个最长为 \(gcd(n,k)\) 的循环节能提供多少的不动点个数:\(m^{gcd(n,k)}\) 因为这个循环节里的颜色我们可以任意选择,也就是说有 \(m\) 种颜色任我们选
根据 Burnside 定理,所以最后的答案就是
考虑所谓的 \(k=0\) 好像是所谓的 \(m^n\) 种可能都是可以的,但是我们考虑一个都不旋转有啥区别吗,需要统计上吗,不需要啊!!!(考虑了大概二十分钟,真的无语。)
对称
分两种情况:
(1)\(n\) 为奇数:
那么有可能成为答案的翻转就是以某一个点和其对应边的中点的连线作为对称轴
那么总共有 \(n\) 个点,也就意味着我们可以选择 \(n\) 个点来进行旋转,也就是意味着我们的置换个数为 \(n\),因为置换数就是指的操作个数。这里来考虑 \(polya\) 定理,也就是求某一个置换的循环个数,这种也就非常好想了。在对称的条件下啥是循环:只有可能是相对应的两个点是一个循环啊,也就是所谓的只可能是 \(a-b,b-a\) 这种的可能,那么去除我们选择的那个点,总共有 \(n-1\) 个点,也就是总共有 \(\frac{n-1}{2}\) 个循环,但是考虑所谓的循环:我们选择的点经过对称会变成我们选择的点,也就是不动,但是在置换里就是一个 \(a,a\) 这也算一个循环啊,所以循环数就是 \(\frac{n+1}{2}\)那么根据 \(polya\) 定理,答案数就是
(2)\(n\) 为偶数:
那么考虑有可能成为答案的翻转 1.选择两个相对应的点的连线 2.选择两个相对应的边的中点的连线
1.选择两个相对应的点的连线作为对称轴来对称,那么这种对称轴的选法不就总共有 \(n/2\) 种吗,也就是所谓的我们的置换个数为 \(n/2\),然后就考虑每一种置换总共有多少个循环。其实也就是跟上面一样的,只有可能是 \(a-b,b-a\) 这种形式,所以最多有 \((n-2)/2\) 个循环,但是我们还选择了两个点所以就有多出来的 \(2\) 个循环,所以就是有 \((n+2)/2\) 个循环,所以根据 \(polya\) 定理,答案数就是
2.选择两个对应的边有 \(n/2\) 种选法,也就是有 \(n/2\) 种对称的方案,所以置换个数就是 \(n/2\)。那么考虑一个置换有多少个循环呢,很明显只有可能是 \(a-b,b-a\) 的形式,所以有 \(n/2\) 个循环,所以根据 \(polya\) 定理可得:
注意我们的结果就是说要把这些所有的情况合并到一起,这里分类讨论只是为了方便去分析。所以最后的答案就是分奇偶之后的答案之和除以置换也就是操作数量,那么置换很神奇的能发现:
奇数:\(n + n = 2n\)
偶数:\(n + \frac{n}{2} + \frac{n}{2} = 2n\)
所以分类统计答案最后直接除以 \(2n\) 然后输出就好了
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
long long quick_power(long long a,long long b){
long long res = 1;
while(b){
if(b&1){
res *= a;
}
b >>=1;
a*=a;
}
return res;
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
long long n,m;
while(1){
cin>>n>>m;
if(n == -1)
break;
if(n == 0){
printf("%d\n",0);
continue;
}
long long ans = 0;
for(long long i=1; i<=n; i++){
ans += quick_power(m,__gcd(i,n));
}
if(n & 1) //奇数
ans += n * quick_power(m,(n+1)/2);
else //偶数
ans += (n/2) * quick_power(m,((n+2)/2)) + (n/2) * quick_power(m,n/2);
ans /= 2*n;
printf("%lld\n",ans);
}
return 0;
}
理解了之后就超级好写的一份代码