「POJ 2154」Color

传送门


problem

给出一个长度为 n n n 的项链,有 n n n 种颜色,请求出有多少种染色方案。

注意:一个项链不一定会用完 n n n 种颜色,并且旋转后相同的染色方案视作同一种。

答案对 P P P 取模。

数据范围: 1 ≤ n ≤ 1 0 9 1 \le n \le 10^9 1n109 1 ≤ P ≤ 30000 1 \le P \le 30000 1P30000


solution

这是一道 Polya 定理的模板题。

首先是 Polya 定理的公式:

1 ∣ G ∣ ∑ i = 1 ∣ G ∣ m k i \frac{1}{|G|}\sum_{i=1}^{|G|}m^{k_i} G1i=1Gmki

其中 ∣ G ∣ |G| G 表示置换的数目, m m m 表示可选颜色数目, k i k_i ki 表示第 i i i 个置换包含的循环个数。

那么对于这道题,由于只有旋转,置换有 n n n 种。对于第 i i i 个置换(即旋 i i i 个珠子),循环的个数是 gcd ⁡ ( n , i ) \gcd(n,i) gcd(n,i),所以推出来的公式是 a n s = 1 n ∑ i = 1 n n gcd ⁡ ( n , i ) ans=\frac 1 n\sum_{i=1}^nn^{\gcd(n,i)} ans=n1i=1nngcd(n,i)

如果直接枚举 n n n 计算答案,时间复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的,要优化。

然后就是推式子的过程了:

a n s = 1 n ∑ i = 1 n n gcd ⁡ ( i , n ) = 1 n ∑ k ∣ n n k ∑ i = 1 n [ gcd ⁡ ( i , n ) = k ] = ∑ k ∣ n n k − 1 ∑ i = 1 n k [ gcd ⁡ ( i , n k ) = 1 ] = ∑ k ∣ n n k − 1 φ ( n k ) \begin{aligned}ans&=\frac 1 n\sum_{i=1}^nn^{\gcd(i,n)}\\&=\frac 1 n\sum_{k|n}n^k\sum_{i=1}^n[\gcd(i,n)=k]\\&=\sum_{k|n}n^{k-1}\sum_{i=1}^{\frac n k}[\gcd(i,\frac n k)=1]\\&=\sum_{k|n}n^{k-1}\varphi(\frac n k)\end{aligned} ans=n1i=1nngcd(i,n)=n1knnki=1n[gcd(i,n)=k]=knnk1i=1kn[gcd(i,kn)=1]=knnk1φ(kn)

我们可以在 O ( n ) O(\sqrt n) O(n ) 的时间一个数的 φ \varphi φ 值(根据公式 φ ( n ) = n ∏ i = 1 k ( 1 − 1 p k ) \varphi(n)=n\prod_{i=1}^k(1-\frac 1 {p_k}) φ(n)=ni=1k(1pk1)。)

然后我们就可以通过这道题了。


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
using namespace std;
int n,P;
int add(int x,int y)  {return x+y>=P?x+y-P:x+y;}
int dec(int x,int y)  {return x-y< 0?x-y+P:x-y;}
int mul(int x,int y)  {return 1ll*x*y%P;}
int power(int a,int b){
	int ans=1;
	for(;b;b>>=1,a=mul(a,a))  if(b&1)  ans=mul(ans,a);
	return ans;
}
int sum,prime[N],mark[N];
void linear_sieves(){
	for(int i=2;i<N;++i){
		if(!mark[i])  prime[++sum]=i;
		for(int j=1;j<=sum&&i*prime[j]<N;++j){
			mark[i*prime[j]]=1;
			if(i%prime[j]==0)  break;
		}
	}
}
int phi(int n){
	int ans=n;
	for(int i=1;prime[i]*prime[i]<=n;++i){
		if(n%prime[i]==0){
			ans=ans/prime[i]*(prime[i]-1);
			while(n%prime[i]==0)  n/=prime[i];
		}
	}
	if(n!=1)  ans=ans/n*(n-1);
	return ans;
}
int main(){
	int T;
	scanf("%d",&T);
	linear_sieves();
	while(T--){
		int ans=0;
		scanf("%d%d",&n,&P);
		for(int i=1;i*i<=n;++i){
			if(n%i==0){
				ans=add(ans,mul(phi(i),power(n,n/i-1)));
				if(i*i!=n)  ans=add(ans,mul(phi(n/i),power(n,i-1)));
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值