\(\frak Description\)

将正 \(n\) 边形的 \(n\) 个顶点用 \(n\) 种颜色染色,问有多少种方案(旋转相同算一种),答案对给定质数 \(p\) 取模。

\(\frak Solution\)

之前写的,太烂了但是舍不得删。

怎么说,这里是记录一个玄学优化吧。

先列出 \(Polya\) 定理吧:

\[ans=\frac{1}{|G|}\sum_{k=1}^{g}m^{c(\sigma_k)} \]

这道题只有旋转,于是我们的 \(|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)\) 级别的。

\[\begin{align} \text{Ans}&=\frac{1}{n}\cdot \sum_{i=0}^{n-1}n^{\gcd(i,n)}\\ &=\frac{1}{n}\cdot \sum_{i=1}^{n}n^{\gcd(i,n)}\\ &=\frac{1}{n}\cdot \sum_{d\mid n}n^d\sum_{i=1}^n [\gcd(i,n)=d]\\ &=\sum_{d\mid n}n^{d-1}\cdot \varphi(n/d) \end{align} \]

推导在我的莫反博客里有提及,这里就不再赘述。

具体实现时,可以预处理 \(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;
}
posted on 2020-04-15 12:10  Oxide  阅读(123)  评论(0编辑  收藏  举报