Hdu 2923 Invoker(数学_Polya)

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

你可能感兴趣的:(Hdu 2923 Invoker(数学_Polya))