\(\frak Description\)
将正 \(n\) 边形的 \(n\) 个顶点用 \(n\) 种颜色染色,问有多少种方案(旋转相同算一种),答案对给定质数 \(p\) 取模。
\(\frak Solution\)
之前写的,太烂了但是舍不得删。
怎么说,这里是记录一个玄学优化吧。
先列出 \(Polya\) 定理吧:
这道题只有旋转,于是我们的 \(|G|==g\) 就是 n 啦,而表示颜色种数的 m 也是 n 啦。
但是康康 n 的范围:\(1e9\) 艹!
于是我们调转枪头,我们发现循环节的长度相同时,此时的 i 不一定相同,这就是我们优化的突破口。
设 L 为循环节长度,易得 \(L=\frac{n}{gcd(i,n)}\),其中 \(gcd(i,n)\) 是循环节的个数。
再设 \(cnt=gcd(i,n)=\frac{n}{L}\)。
因为 \(cnt|i\),所以可令 \(i=t*cnt\)。(因为 \(1<=i<=n\),所以 \(1<=t<=\frac{n}{cnt}=L\))
所以有:\(gcd(i,n)==gcd(t*cnt,L*cnt)==cnt\)。
上面这个式子就是使循环节长度为 L 的条件。
易得当 \(gcd(t,L)==1\) 时满足条件。
L 是可以以 \(O(\sqrt n)\) 的复杂度枚举的,求出此时合法的 t 的个数就转换成了求 \(phi(L)\)。
然后直接套上去就行了。
注意的是我们这里用两种求 \(phi\) 的方法为了降低点运行时间。直接算 \(phi\) 的那个方法可以证明我们之前筛的素数是够用的,因为我们先前筛的素数是 \(1e6\) 范围内,而 n 的范围是 \(1e9\) 之内,可以保证超过我们预处理范围的素数至多只有一种。
就是 \(\rm Polya\) 定理的一个板子,答案就是 \(\frac{1}{n}\cdot \sum_{g\in G}n^{c(g)}=\frac{1}{n}\cdot \sum_{i=0}^{n-1}n^{\gcd(i,n)}\).
接下来证明正 \(n\) 边形顺时针移动 \(i\) 位时,形成的互不相交的 "环" 的个数为 \(\gcd(i,n)\).
假设对于某个 "环" 的开头为 \(x\),那么这个 "环" 包含 \(x,x+i,x+2i,...,x+ki\),不妨假设在 \(x+ki\) 时 第一次 回到开头,那么有 \(x\equiv x+ki\pmod n\),从而 \(ki\equiv 0\pmod n\).
那么 \(k=\frac{\text{lcm}(i,n)}{i}=\frac{n}{\gcd(n,i)}\),我们注意到 \(k\) 实际是每个 "环" 的长度,所以互不相交的 "环" 的个数为 \(n/k=\gcd(i,n)\).
但是 \(n\) 的范围很大,所以需要继续优化。一个很自然的想法是枚举 \(n\) 的约数即 \(\gcd(i,n)\),它是 \(\mathcal O(\sqrt n)\) 级别的。
推导在我的莫反博客里有提及,这里就不再赘述。
具体实现时,可以预处理 \(10^6\) 之内的欧拉函数值,再拼上 \(\mathcal O(\sqrt n)\) 求欧拉函数。
\(\frak{Code}\)
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' || s<'0')
f |= (s=='-');
while(s>='0' && s<='9')
x = (x<<1)+(x<<3)+(s^48),
s = getchar();
return f?-x:x;
}
template <class T>
inline void write(T x) {
static int writ[50],tp=0;
if(x<0) putchar('-'),x=-x;
do writ[++tp] = x-x/10*10, x /= 10; while(x);
while(tp) putchar(writ[tp--]^48);
}
const int maxn = 1e6+5;
bool is[maxn];
int n,mod,pc;
int p[maxn],phi[maxn];
void sieve() {
phi[1] = 1;
for(int i=2;i<=maxn-5;++i) {
if(!is[i])
p[++pc] = i,
phi[i] = i-1;
for(int j=1; j<=pc && i*p[j]<=maxn-5; ++j) {
is[i*p[j]] = 1;
if(i%p[j]==0) {
phi[i*p[j]] = phi[i]*p[j];
break;
} else phi[i*p[j]] = phi[i]*(p[j]-1);
}
}
}
int qkpow(int x,int y) {
int r=1;
while(y) {
if(y&1) r=1ll*r*x%mod;
x=1ll*x*x%mod; y>>=1;
}
return r;
}
inline int inc(int x,int y) {
return x+y>=mod?x+y-mod:x+y;
}
int Phi(int x) {
if(x<=maxn-5) return phi[x]%mod;
int ans=x;
for(int i=1; i<=pc && p[i]*p[i]<=x; ++i)
if(x%p[i]==0) {
ans -= ans/p[i];
while(x%p[i]==0) x/=p[i];
}
if(x>1) ans -= ans/x;
return ans%mod;
}
int Polya() {
int ans=0;
for(int i=1;i*i<=n;++i) {
if(n%i) continue;
ans = inc(ans,1ll*Phi(i)*qkpow(n,n/i-1)%mod);
if(i*i==n) break;
ans = inc(ans,1ll*Phi(n/i)*qkpow(n,i-1)%mod);
}
return ans;
}
int main() {
sieve();
for(int T=read(9);T;--T) {
n=read(9),mod=read(9);
print(Polya(),'\n');
}
return 0;
}