所属题型:组合数学
要求知识:polya或者burnside
题意转述:一串由n个珠子组成的项链,用c种颜色涂染,问能形成多少种不同项链。
限制:旋转得来的为同一种,翻转得来的也为同一种。
本题有多种算法,下面给出比较容易想到且易于理解的两种算法。
推荐使用算法2,因为若是碰到高精度问题,算法2比算法1易于处理,且复杂度也比算法1低好多。核心知识点都是Polya定理。
解决过程:
算法一:
I.计算旋转置换和翻转置换的个数:
1.旋转置换
N个珠子,把项链当成一个圆,每次旋转角度为I*360/N(I∈(1,n)),共有N次不同旋转。
2.翻转置换
⊙考虑对称轴:
n为奇数. 只有一种对称轴, 即轴穿过一个点. 这样有N种置换。
n为偶数, 有两种翻转:
轴每边n/2个点. 这样的置换有n/2个;
轴穿两点, 每边n/2个点. 这样的置换也有n/2个;
这样算出来结果还是有n个置换,即n为奇偶数是置换个数是一样的。
综上所述,共有2*N次不同置换,即有|G|=2N。
II.每个置换的循环节数
1.旋转置换
第i次旋转对应的置换结果:
[c-sharp] view plain copy print ?
- for(j=0; j<n; j++)
- {
- g[j]=(i+j)%n;
- }
- 循环节数count由一下循环求出:
- for(j=0, count=0; j<n; j++)
- {
- if(flag[j]==0)
- {
- count++;
- for(k=j; flag[k]==0; k=g[k])
- flag[k]=1;
- }
- }
for(j=0; j<n; j++) { g[j]=(i+j)%n; } 循环节数count由一下循环求出: for(j=0, count=0; j<n; j++) { //求每个旋转置换的循环节数,和寻找连通子图类似. if(flag[j]==0) { //若j结点未曾访问过,表示一个新的循环,count++. count++; for(k=j; flag[k]==0; k=g[k]) flag[k]=1; } }
2.翻转置换
第i次翻转对应的置换结果:
[c-sharp] view plain copy print ?
- for(j=1; (j+j)<n; j++)
- {
- k=g[j];
- g[j]=g[n-j];
- g[n-j]=k;
- }
- 循环节数count由以下循环求出:
- for(j=0, count=0; j<n; j++)
- {
- if(flag[j]==0)
- {
- count++;
- for(k=j; flag[k]==0; k=g[k])
- flag[k]=1;
- }
- }
for(j=1; (j+j)<n; j++) { //每次翻转置换的结果,沿翻转轴对称点交换位置. //由于程序里每次旋转置换在翻转置换之前进行,故每次只要用 //同一个翻转循环处理,而不必分情况讨论. k=g[j]; g[j]=g[n-j]; g[n-j]=k; } 循环节数count由以下循环求出: for(j=0, count=0; j<n; j++) { //求每个翻转置换的循环节数,和寻找连通子图类似. if(flag[j]==0) { //若j结点未曾访问过,表示一个新的循环,count++. count++; for(k=j; flag[k]==0; k=g[k]) flag[k]=1; } }
算法二:
本题还可以快速算出每个置换的循环(或者轮换)个数,达到快速求解的目的:
分析(1)
1.旋转.
考虑顺时针旋转i格的置换:
循环个数为gcd(n,i)
每个循环的长度为L=n/gcd(n,i)
2.翻转
考虑对称轴
***n为奇数. 只有一种对称轴, 即轴穿过一个点. 有[n/2]个循环长度为2, 还有一个循环长度为1(被穿过的点), V=C([n/2], [m/2]).
***n为偶数, 有两种翻转
轴每边n/2个点. 这样的置换有n/2个
轴穿两点, 每边n/2个点. 这样的置换也有n/2个
算法复杂度分析:很容易可以算出这个算法的时间复杂度为O(n)。
分析(2):
Polya定理:设G是n个对象的一个置换群,用m种颜色涂染这n个对象,则不同染色的方案数L=1/|G|*[m^p(a1)+m^p(a2)+....+m^p(an)].其中p(ai)是某个置换的循环数.
1.旋转置换.
我们假设依次顺时针旋转1~n个,则循环个数=gcd(i,n);
2.翻转置换
当n为偶数时,分两种情况,一种是中心轴在两个对称对象上,则循环个数为n/2+1,另一种是对称轴两边分别有n/2个对象,则循环个数为n/2;
当n为奇数时,对称轴就只能在一个对象上,则循环个数为n/2+1;
[c-sharp] view plain copy print ?
- void polya(int p, int n)
- {
- int i;
- ans = 0;
- for(i=0; i<n; i++)
- ans += pow(p*1.0, gcd(n, i));
- if(n % 2)
- {
- for(i=0; i<n; i++)
- ans += pow(p*1.0, n/2+1);
- }
- else
- {
- for(i=0; i<n/2; i++)
- ans += pow(p*1.0, n/2) + pow(p*1.0, n/2+1);
- }
- }
void polya(int p, int n) { int i; ans = 0; for(i=0; i<n; i++) //第i次旋转的循环节数为gcd(n,i); ans += pow(p*1.0, gcd(n, i)); if(n % 2) //n为奇数 { for(i=0; i<n; i++) //共有n个循环节数均为 n/2+1 的置换; ans += pow(p*1.0, n/2+1); } else //n为偶数 { for(i=0; i<n/2; i++) //如上面所述有两种置换,第一种循环节数均为n/2, ans += pow(p*1.0, n/2) + pow(p*1.0, n/2+1); //第二种循环节数均为n/2+1; } }