Description
将正 n 边形的 n 个顶点用 n 种颜色染色,问有多少种方案(旋转相同算一种),答案对给定质数 p 取模。
Solution
之前写的,太烂了但是舍不得删。
怎么说,这里是记录一个玄学优化吧。
先列出 Polya 定理吧:
这道题只有旋转,于是我们的 |G|==g 就是 n 啦,而表示颜色种数的 m 也是 n 啦。
但是康康 n 的范围:1e9 艹!
于是我们调转枪头,我们发现循环节的长度相同时,此时的 i 不一定相同,这就是我们优化的突破口。
设 L 为循环节长度,易得 L=ngcd(i,n),其中 gcd(i,n) 是循环节的个数。
再设 cnt=gcd(i,n)=nL。
因为 cnt|i,所以可令 i=t∗cnt。(因为 1<=i<=n,所以 1<=t<=ncnt=L)
所以有:gcd(i,n)==gcd(t∗cnt,L∗cnt)==cnt。
上面这个式子就是使循环节长度为 L 的条件。
易得当 gcd(t,L)==1 时满足条件。
L 是可以以 O(√n) 的复杂度枚举的,求出此时合法的 t 的个数就转换成了求 phi(L)。
然后直接套上去就行了。
注意的是我们这里用两种求 phi 的方法为了降低点运行时间。直接算 phi 的那个方法可以证明我们之前筛的素数是够用的,因为我们先前筛的素数是 1e6 范围内,而 n 的范围是 1e9 之内,可以保证超过我们预处理范围的素数至多只有一种。
就是 Polya 定理的一个板子,答案就是 1n⋅∑g∈Gnc(g)=1n⋅∑n−1i=0ngcd(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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?