polya定理主要是用于解决等价类计数问题的,所谓等价类计数问题是指题目中会定义一种等价关系,满足这个关系的元素都会被看成同一类,并只需要统计一次,最终需要统计所有的不同方案数。
首先,需要理解等价关系,我们说两个元素是等价的,那么意味着它们满足我们给出的等价条件。等价关系还满足自反性、对称性以及传递性。最终,所有的元素就会被分成若干个等价类,每一个等价类中的元素相互等价,算作一种,不同等价类中的元数则不等价,算作两种。
然后,自然我们就会想去统计等价类的个数。但是,先必须得想办法描述等价关系,一般我们使用置换集合F来描述,如果其中一个置换将一个方案映射到了另一个方案,那么说明二者是等价的。并且置换集合F必须满足,其中任意两个置换的乘积也必须在F中。
接着,我们来看看Burnside定理:如果某一个方案s经过一个置换f后不变,则称s为不动点,将f的所有不动点数目记为c(f),那么等价类数目就等于所有置换的c(f)的平均值。如此一来,我们就可以计算出我们想知道的等价类的数目了。
最后,关键来了,如何计算c(f)呢?以经典的涂色问题为例,如果将置换f分解成m(f)个循环的乘积,并假设涂k种颜色的话,那么c(f)等于k^m(f),详见<<训练指南>>,置换及其应用一章。结合Burnside定理,最终就得到了polya定理:所有置换的k^m(f)的平均值就是等价类的个数。
下面来看看一些典型的等价类置换,背景都是由n个珠子构成的环,并且珠子的颜色总数为t。
1、旋转
如果旋转i颗珠子的间距,那么一共有gcd(i,n)个循环,所以由前面的分析可知其不动点总数为a=t^gcd(0,n) + t^gcd(1,n) + ... + t^gcd(n-1,n)。
2、翻转
分为两种情况。n为奇数时,对称轴有n条,每一条形成(n+!)/2个循环,所以不动点总数为b=n*t^((n+1)/2);当n为偶数时,穿过珠子的对称轴有n/2条,每一条都形成n/2+1个循环,不穿过珠子的对称轴有n/2条,每一条都形成n/2个循环。所以,不动点的总数为b=n/2*(t^(n/2+1)+t^(n/2))。
最后根据polya定理可知,只具有旋转性质的等价类有a/n种,只具有翻转性质的有b/n种,两者都具有的有(a+b)/(2*n)种。由此可知,polya定理的简单应用的关键就是找出置换集合,进而推出不动点的数目。
下面具体看一道题,HDOJ:3923,时空转移(点击打开链接),题目如下:
Invoker
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 122768/62768 K (Java/Others)Total Submission(s): 1164 Accepted Submission(s): 489
In his new map, Kael can control n kind of elements and he can put m elements equal-spacedly on a magic ring and combine them to invoke a new skill. But if a arrangement can change into another by rotate the magic ring or reverse the ring along the axis, they will invoke the same skill. Now give you n and m how many different skill can Kael invoke? As the number maybe too large, just output the answer mod 1000000007.
For each test case: give you two positive integers n and m. ( 1 <= n, m <= 10000 )
2 3 4 1 2
Case #1: 21 Case #2: 1HintFor Case #1: we assume a,b,c are the 3 kinds of elements. Here are the 21 different arrangements to invoke the skills / aaaa / aaab / aaac / aabb / aabc / aacc / abab / / abac / abbb / abbc / abcb / abcc / acac / acbc / / accc / bbbb / bbbc / bbcc / bcbc / bccc / cccc /
用n种颜色涂有m个点的环,求通过旋转和翻转后都不相同的涂色方法。
分析:
正是前面说到的典型等价类置换,用代码实现即可。
源代码:
#include <cstdio>
#define LL __int64
const LL mm=1000000007;
LL mod, pow[10005];
int n, m;
LL gcd(LL a, LL b)
{
return b==0 ? a : gcd(b,a%b);
}
LL rotat() //旋转时的不动点
{
LL ans = 0;
for (int i=0; i<m; ++i)
ans = (ans+pow[gcd(i,m)]) % mod;
return ans;
}
LL overturn() //翻转时的不动点,分奇偶讨论
{
LL ans = 0;
if(m & 1)
ans = (ans+m*pow[(m+1)/2]) % mod;
else
ans = (ans+m/2*(pow[m/2+1]+pow[m/2])) % mod;
return ans;
}
int main ()
{
int cas, t=0;
scanf("%d", &cas);
while(cas--)
{
LL ans = 0;
scanf("%d%d", &n, &m);
mod = 2*m*mm;
pow[0] = 1;
for(int i=1; i<=m; ++i)
pow[i] = (pow[i-1]*n) % mod;
ans += rotat();
ans = (ans + overturn()) % mod;
printf("Case #%d: %I64d\n", ++t, (ans/2/m)%mm);
}
return 0;
}
其他类似的题目还有,HDOJ:2084、2647、1812、3411、2865、2481。POJ:1286、2409、2154、2888。