poj 1286 Necklace of Beads (组合数学 polya计数法)

大致题意:给你三种颜色的珠子,和一个长度为N(N<24)的项链,用这三种珠子串成这个项链,项链可以旋转和翻转,经过旋转和翻转所得的项链视为同一种项链,现在告诉你项链的长度s,求共能组成几条不同的项链。

Time Limit : 2000/1000ms (Java/Other)   Memory Limit : 20000/10000K (Java/Other)

 

                   

样例:

Sample Input
4 5 -1
 

 

Sample Output
21 39

 

看到这个题的题意,肯定是计数法没跑啦,而且是那种最水的,颜色固定就3种,而且项链的长度也很小,而且也没有条件限制,模版稍稍一套就能过,连优化算法都不用上,稍稍被坑在s=0的情况上了。

思路:用polya 定理解决这个计数问题,关键是分析群中置换的个数和每个置换的循环节个数,分析题目中的两种置换群,一种是旋转置换,一种是翻转置换,这两种变换都是刚性变换。

 

对于旋转置换群,群内置换的总个数显而易见是n个(转n次就返回到自己了嘛),第i个置换中循环节的个数可以用dfs搜索出来,不过有直接的结论,循环节个数应该是gcd(n,i)个,这个结论所有博客都给出了,但是几乎都没给证明(大神都觉得太显而易见了吧)。

在此我给出证明:在任意一个旋转中,不妨设旋转的角度为i(0=<i<n)弧度,任意选取一个点x,那么从x开始以i弧度为单位进行旋转,那么它将经过LCM(i,n)/i次旋转回到x自己。想不清楚可以举个例子看看,就拿上面图的n=4,i=2为例,那个green的球经过两次弧度为i=2的旋转就回到了自己。明白了这个问题就迎刃而解了,由于选取的点是任意的,所以所有点所在的循环节的长度就是LCM(i,n)/i,这也就说明在旋转置换中所有节的长度都相等,n/(LCM(i,n)/i)应该就是循环节的个数了,n/(LCM(i,n)/i)=gcd(n,i)这很明显,所以给出的结论成立。注意:此结论得出的循环节个数只适用于二维旋转置换,别乱用啊。

 

对于翻转置换,看上面的那个图,它已经给了你很大提示,找循环节的关键是找对称轴。这里n要分奇偶性。

当n为奇数,那么对称轴就是每个点和圆心的连线,共n条(观察第二个图),那么显然除了这个点没变,其他的点都跟对称的那个点置换了,所以循环节的个数是(n-1)/2+1。

当n为偶数,那么对称轴有每个点和对面的点的连线,共n/2条,显然除了对称轴上的两个点,其余点都跟对面的点置换了循环节的个数是(n-2)/2+2,两个相邻点中点和圆心的连线也是n/2条,显然每个点都跟对面的点置换了,循环节的个数是n/2,n为偶也是n条对称轴,(观察第一个图)。

分析至此,求这个题就没难度了,由于我采取了最笨的枚举i法,所以时间复杂度O(sp),s为置换个数,p为项链珠子数,但是A此题还是妥妥的。

 

多说几句,polya定理是在burnside引理的基础上得出的结论,能快速求一个置换下不变元素(稳定核)的个数,但它不是万能的,如果条件多的计数,还是burnside好使。

个人非常喜欢做计数法的题,感觉出的题目都很有意思,而且计数法的题难度还是比较大的,限制条件多了就很难搞,以后有时间会针对用burnside定理和polya定理解决计数问题作总结。

 

 1 #include<cstdio>
 2 #include<cmath>
 3 using namespace std;
 4 typedef __int64 ll;
 5 int gcd(int a,int b){
 6     if(b==0){
 7         return a;
 8     }
 9     return gcd(b,a%b);
10 }
11 int c=3,s;
12 ll polya(){
13     int i,j;
14     ll ans=0;
15     for(i=0;i<s;i++){
16         ans+=(ll)pow(1.0*c,gcd(s,i));
17     }
18     if(s%2){
19         ans+=s*(ll)pow(1.0*c,s/2+1);
20     }
21     else{
22         ans+=s/2*(ll)pow(1.0*c,s/2);
23         ans+=s/2*(ll)pow(1.0*c,s/2+1);
24     }
25     return ans/2/s;
26 }
27 int main(){
28     ll ans;
29     while(scanf("%d",&s)){
30         if(s==-1)break;
31         if(s==0)ans=0;
32         else ans=polya();
33         printf("%I64d\n",ans);
34     }
35     return 0;
36 }
posted @ 2012-06-20 12:53  McFlurry  阅读(1063)  评论(0编辑  收藏  举报