怎么这么多阅读啊……
这篇文章是群论(其实只有Polya)在信息学奥赛中计数的运用,并不是群论的讲义……
群论可以说是数学中最高深的内容,一个oier去深入了解是不现实的。因此,我们只需要知道结论。
本文主要讲解群论在oi中辅助计数的运用,将尽量做到通俗易懂。
Luogu P4980
题意:给一个长度为NNN的环,染NNN种色,旋转同构,求方案数
N≤1e9N \leq 1e9N≤1e9
基本概念
置换 一个排列ppp,表示iii可以变成pip_ipi
本题中,旋转后得到的排列就是置换。
(1,2,3,...,n)(2,3,4,...,n,1)......(n,1,2,...,n−1)(1,2,3,...,n)(2,3,4,...,n,1)... ...(n,1,2,...,n-1)(1,2,3,...,n)(2,3,4,...,n,1)......(n,1,2,...,n−1)都是置换
置换群一堆置换
置换间可以相乘。规则是对每个iii依次进行每个置换
本题中,上述nnn个置换构成置换群
轨道某个数aaa,经过若干置换(可以为1)回到自己,经过的数在一个轨道上。
人话:环
本题中,若n=6n=6n=6,对于置换(3,4,5,6,1,2)(3,4,5,6,1,2)(3,4,5,6,1,2),3−>5−>13->5->13−>5−>1和4−>6−>24->6->24−>6−>2是两个轨道
Burnside引理
本质不同的方案数等于每个置换跑了之后不变化的方案数的平均值
人话:对于每个置换,把每个轨道缩成点,求出当前方案数,所有方案的和除以总置换数就是本质不同的方案数。
这样可以枚举置换,然后暴力跑出置换数,可以做到O(n2)O(n^2)O(n2)
然而这题有个特殊性质,容(bu)易(yong)证明
轨道数=gcd(旋转次数,n)\text{轨道数=gcd(旋转次数,n)}轨道数=gcd(旋转次数,n)
这样可以得到公式:
Ans=1n∑i=1nngcd(i,n)Ans=\frac{1}{n}\sum_{i=1}^n n^{gcd(i,n)}Ans=n1∑i=1nngcd(i,n)
如果我没理解错,这个就是polyapolyapolya定理
真心不知道有啥区别
可以做到O(nlogn)O(nlogn)O(nlogn)
优化
回到
Ans=1n∑i=1nngcd(i,n)Ans=\frac{1}{n}\sum_{i=1}^n n^{gcd(i,n)}Ans=n1∑i=1nngcd(i,n)
我们发现gcd(i,n)gcd(i,n)gcd(i,n) 只有O(n)O(\sqrt{n})O(n)种取值
可以枚举gcd(i,n)gcd(i,n)gcd(i,n)
Ans=1n∑d∣n∑i=1nd[gcd(i,nd)=1]ndAns=\frac{1}{n}\sum_{d|n}\sum_{i=1}^{\frac{n}{d}}[gcd(i,\frac{n}{d})=1]n^dAns=n1∑d∣n∑i=1dn[gcd(i,dn)=1]nd
不就是欧拉函数吗
Ans=1n∑d∣nϕ(nd)nd=∑d∣nϕ(nd)nd−1Ans=\frac{1}{n}\sum_{d|n}\phi(\frac{n}{d})n^d=\sum_{d|n}\phi(\frac{n}{d})n^{d-1}Ans=n1∑d∣nϕ(dn)nd=∑d∣nϕ(dn)nd−1
然后是暴力枚举ddd,暴力算ϕ\phiϕ,复杂度O(n34)O(n^{\frac{3}{4}})O(n43)
并不会证明
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
using namespace std;
typedef long long ll;
const int MOD=1e9+7;
inline int qpow(int a,int p)
{int ans=1;while (p){if (p&1) ans=(ll)ans*a%MOD;a=(ll)a*a%MOD,p>>=1;}return ans;
}
inline int phi(int x)
{int ans=x;for (int i=2;i*i<=x;i++)if (x%i==0){ans/=i,ans*=i-1;while (x%i==0) x/=i;}if (x>1) ans/=x,ans*=x-1;return ans;
}
int n;
int calc(const int&d){return (ll)phi(n/d)*qpow(n,d-1)%MOD;}
int main()
{int T;scanf("%d",&T);while (T--){int ans=0;scanf("%d",&n);for (int i=1;i*i<=n;i++)if (n%i==0) {ans=(ans+calc(i))%MOD;if (i*i<n) ans=(ans+calc(n/i))%MOD;}printf("%d\n",ans);}return 0;
}