problem
给出一个长度为 n n n 的项链,有 n n n 种颜色,请求出有多少种染色方案。
注意:一个项链不一定会用完 n n n 种颜色,并且旋转后相同的染色方案视作同一种。
答案对 P P P 取模。
数据范围: 1 ≤ n ≤ 1 0 9 1 \le n \le 10^9 1≤n≤109, 1 ≤ P ≤ 30000 1 \le P \le 30000 1≤P≤30000。
solution
这是一道 Polya 定理的模板题。
首先是 Polya 定理的公式:
1 ∣ G ∣ ∑ i = 1 ∣ G ∣ m k i \frac{1}{|G|}\sum_{i=1}^{|G|}m^{k_i} ∣G∣1i=1∑∣G∣mki
其中 ∣ 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=n1∑i=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=1∑nngcd(i,n)=n1k∣n∑nki=1∑n[gcd(i,n)=k]=k∣n∑nk−1i=1∑kn[gcd(i,kn)=1]=k∣n∑nk−1φ(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)=n∏i=1k(1−pk1)。)
然后我们就可以通过这道题了。
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;
}