poj 2154 Color Polya定理 欧拉函数优化

题意:有一个长度为n的项链,项链上每颗钻石有n种染色方案,问有多少种方案


思路:置换都考虑用Polya定理做,但是会达到o(n)级别,这里n太大,会超时。可以换个思路,一般做法是i从1枚举到n,求每一个gcd(i, n),可以看到一个性质是每一个   gcd(i, n)都是n的约数,而n的约数是有限的,也就是10^9个gcd(i, n)会有大量的重复,那么我们可以枚举每一个约数。

例如d = gcd(k, n),显然k = d * t,d = gcd(k, n) = gcd(d * t, n) = d * gcd(t, n / d)。明显,gcd(t, n / d)必然等于1,那么求有多少个gcd(k, n)就是求n / d的欧拉函数是多大。这道题有个地方要注意下,当一个数比较大时,它的欧拉函数也会很大,所以要记得取模。还有个可以优化的地方,可以事先打印出质因子或者打印出n的质因子,这样做欧拉函数的复杂度能再一步减少。并且项链翻转后不是等价的,而手镯是翻转后是等价的,所以下面置换没有考虑翻转

感觉这题真的挺好的,考了数论基础的知识,但要对知识比较熟悉才能ac


题目链接:http://poj.org/problem?id=2154


#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <iostream>
using namespace std;

vector<int> divisor(int n)//打印出n的所有因子
{
	vector<int> res;
	int m = (int)floor(sqrt(n * 1.0) + 0.5);
	for(int i = 1; i <= m; i++)
	{
		if(n % i == 0)
		{
			res.push_back(i);
			if(i != n / i) res.push_back(n / i);
		}
	}
	return res;
}

vector<int> prime_factor(int n)//打印出n的所有质因子
{
	vector<int> res;
	int m = (int)floor(sqrt(n * 1.0) + 0.5); 
	for(int i = 2; i <= m; i++)
	{
		if(n % i == 0)
		{
			res.push_back(i);
			while(n % i == 0) n /= i;
		}
	}
	if(n > 1) res.push_back(n);
	return res;
}

int quick_pow(int x, int n, int mod)
{
	int res = 1;
	x %= mod;//这里可以先处理下,当然这里因为mod比较小的情况,如果mod比较大,那么要开longlong或快速乘
	while(n)
	{
		if(n & 1) res = res * x % mod;
		x = x * x % mod;
		n >>= 1;
	}
	return res;
}

int euler_phi(int n, const vector<int> &prime)
{
	int m = prime.size();
	int res = n;
	for(int i = 0; i < m; i++)
	{
		if(n % prime[i] == 0)
		{
			res = res / prime[i] * (prime[i] - 1);
			while(n % prime[i] == 0) n /= prime[i];
		}
	}
	if(n > 1) res = res / n * (n - 1);
	return res;
}

int polya(int n, int mod)
{
	vector<int> divs = divisor(n);
	vector<int> primes = prime_factor(n);//可以预先处理n的质因子
	int ans = 0;
	int m = divs.size();
	for(int i = 0; i < m; i++)
	{
		int euler = euler_phi(divs[i], primes) % mod;//这里要注意取模,当n大时,他的欧拉函数有也会很大
		ans += euler * quick_pow(n, n / divs[i] - 1, mod) % mod;
		ans %= mod;
	}
	return ans % mod;
}

int main()
{
	//freopen("/home/zyh/duipai/data.in", "r", stdin);
	//freopen("/home/zyh/duipai/out1.out", "w", stdout);
	int t, n, mod;
	scanf("%d", &t);
	while(t--)
	{
		scanf("%d%d", &n, &mod);
		int ans = polya(n, mod);
		printf("%d\n", ans);
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值