题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3923
题目大意:用m种颜色的珠子组成长度为n的项链,求方案数,如果经过旋转或者翻转后的方案与之前的某个相同,那它们只算1种方案,最终答案对1000000007取模,n,m<=1万
解题思路:Polya模板题,不过要用到乘法逆元。在写这份解题报告的时候我的内心十分忐忑,因为这题我用好多模版,Polya两种求法、乘法逆元两种求法、欧拉函数、二分快速幂等等,这么多模版都要被“偷”了....最终为人民服务理念战胜了私念,我还是写了这篇报告。呵呵,以上纯属玩笑,勿当真..
Polya的核心是找出各种置换。这题的置换分两种:旋转和翻转。旋转置换有n个,翻转置换也有n个。每个旋转置换循环节个数为cnt=Gcd(i,n),i表示旋转i次i/n*360度,循环节长度len代表len个柱子颜色必须相同,算是一坨吧,cnt表示有cnt坨,显然cnt * len = n. 翻转置换有n个,但置换中循环节个数要分类讨论:1、当n为奇数时,以n个珠子中的任意个珠子连对面空位置的线为对称轴翻转,循环节个数为n/2+1, 2、当n为偶数时,以n个珠子中的对角两个珠子为对称轴进行翻转,循环节个数为n/2+1,这种置换n/2,以n个珠子的对角四个珠子的空隙连线为对称轴翻转,循环节个数为n/2,这种置换有n/2个。
上面的方法效率并不是最高的,因为在求旋转置换的时候同一个循环节长度len可能出现多次,这完全可以避免。
上面的方法枚举的是旋转的次数,实质是角度,我们换种思路枚举循环节个数,这里也设为cnt,原来的cnt = Gcd(i,n),i = cnt * L,n = cnt * t,显然L <= t,且L与t互质。那么我们枚举cnt从1到sqrt(n),还要求循环节个数为cnt的数量,这个数量其实就是i的欧拉函数,就是比它小的质数个数。有一点需要注意,当我们枚举i的时候,如果n%i==0,那么n%(n/i)==0,也就是说枚举i其实枚举i和n/i。
最后利用Polya定理计算总方案数。因为要除以2*n,这样取模的话会出问题,所以必须用乘法逆元来进行计算。
测试数据:
Input:
5
3 4
4 3
1 2
2 1
10000 10000
OutPut:
Case #1: 21
Case #2: 20
Case #3: 1
Case #4: 2
Case #5: 821382349
C艹代码:
#include <stdio.h> #include <string.h> #define MOD 1000000007 #define int64 __int64 int64 ans; int n,m; int Gcd(int x,int y) { //返回最小公约数 int r = x % y; while (r) { x = y,y = r; r = x % y; } return y; } int64 Eular(int64 n){ //欧拉函数,返回小于n大于0与n互质的个数 int64 ans = n, i; for (i = 2; i * i <= n; i++){ if(n % i == 0) { ans -= ans / i; while (n % i == 0) n /= i; if(n == 1) break; } } if (n != 1) ans -= ans / n; return ans % MOD; } int64 Cal(int64 n,int64 k) { //二分快速幂 int64 x = 1; while (k) { if (k & 1) x = (x * n) % MOD; n = (n * n) % MOD,k >>= 1; } return x; } int64 Extend_euclid(int64 a,int64 b,int64 &x,int64 &y){ //比较直观的求逆元 int64 d = 0,t = 0; if(b == 0){ x = 1,y=0; return a; } else{ d = Extend_euclid(b,a%b,x,y); t = x,x = y; y = t - a / b * y; } return d; } int64 Bignum_Div(int64 a,int64 b,int64 mod){ //return a / b % mod int64 x = 0,y = 0; Extend_euclid(b,mod,x,y); x = (x + mod) % mod; return a * x % mod; } int64 inv(int64 x) { //简洁版求逆元 if(x == 1) return 1; return inv(MOD%x) * (MOD - MOD/x) % MOD; } int64 Polya_Putong() { //普通的求解方法 //循环节长度为Gcd(i,n) int i,j,k; ans = Cal(m,n); for (i = 1; i < n; ++i) { k = Gcd(i,n); ans = (ans + Cal(m,k)) % MOD; } if (n % 2 == 1) { k = n / 2 + 1; ans = (ans + n * Cal(m,k) % MOD) % MOD; } else { k = n / 2; ans = (ans + n / 2 * Cal(m,k) % MOD) % MOD; k = n / 2 + 1; ans = (ans + n / 2 * Cal(m,k) % MOD) % MOD; } return ans * inv(2 * n) % MOD;//Bignum_Div(ans,2*n,MOD); } int64 Polya_2B() { //优化后的方法,枚举循环节个数i //Gcd(i*t,i*L)==1,L<t且L与t互质,循环节为i的数量为Eular(t)=Eular(n/i) int i,j,k; ans = 0; for (i = 1; i * i < n; ++i) //枚举循环节个数 if (n % i == 0) { ans = (ans + Eular(n/i) * Cal(m,i) % MOD) % MOD; ans = (ans + Eular(i) * Cal(m,n/i) % MOD) % MOD; } if (i * i == n) ans = (ans + Eular(n/i) * Cal(m,i) % MOD) % MOD; if (n % 2 == 1) { k = n / 2 + 1; ans = (ans + n * Cal(m,k) % MOD) % MOD; } else { k = n / 2; ans = (ans + n / 2 * Cal(m,k) % MOD) % MOD; k = n / 2 + 1; ans = (ans + n / 2 * Cal(m,k) % MOD) % MOD; } return ans * inv(2 * n) % MOD;//Bignum_Div(ans,2*n,MOD); } int main() { int i,j,k,t,Cas = 0; scanf("%d",&t); while (t--) { scanf("%d%d",&m,&n); //ans = Polya_Putong ans = Polya_2B(); printf("Case #%d: %I64d\n",++Cas,ans); } }
本文ZeroClock原创,但可以转载,因为我们是兄弟。