题目链接: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原创,但可以转载,因为我们是兄弟。