怎么这么多阅读啊……
这篇文章是群论(其实只有Polya)在信息学奥赛中计数的运用,并不是群论的讲义……
群论可以说是数学中最高深的内容,一个oier去深入了解是不现实的。因此,我们只需要知道结论。
本文主要讲解群论在oi中辅助计数的运用,将尽量做到通俗易懂。
Luogu P4980
题意:给一个长度为 N N N的环,染 N N N种色,旋转同构,求方案数
N ≤ 1 e 9 N \leq 1e9 N≤1e9
置换 一个排列 p p p,表示 i i i可以变成 p i p_i pi
本题中,旋转后得到的排列就是置换。
( 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)都是置换
置换群一堆置换
置换间可以相乘。规则是对每个 i i i依次进行每个置换
本题中,上述 n n n个置换构成置换群
轨道某个数 a a a,经过若干置换(可以为1)回到自己,经过的数在一个轨道上。
人话:环
本题中,若 n = 6 n=6 n=6,对于置换 ( 3 , 4 , 5 , 6 , 1 , 2 ) (3,4,5,6,1,2) (3,4,5,6,1,2), 3 − > 5 − > 1 3->5->1 3−>5−>1和 4 − > 6 − > 2 4->6->2 4−>6−>2是两个轨道
本质不同的方案数等于每个置换跑了之后不变化的方案数的平均值
人话:对于每个置换,把每个轨道缩成点,求出当前方案数,所有方案的和除以总置换数就是本质不同的方案数。
这样可以枚举置换,然后暴力跑出置换数,可以做到 O ( n 2 ) O(n^2) O(n2)
然而这题有个特殊性质,容(bu)易(yong)证明
轨道数=gcd(旋转次数,n) \text{轨道数=gcd(旋转次数,n)} 轨道数=gcd(旋转次数,n)
这样可以得到公式:
A n s = 1 n ∑ i = 1 n n g c d ( i , n ) Ans=\frac{1}{n}\sum_{i=1}^n n^{gcd(i,n)} Ans=n1∑i=1nngcd(i,n)
如果我没理解错,这个就是 p o l y a polya polya定理
真心不知道有啥区别
可以做到 O ( n l o g n ) O(nlogn) O(nlogn)
回到
A n s = 1 n ∑ i = 1 n n g c d ( i , n ) Ans=\frac{1}{n}\sum_{i=1}^n n^{gcd(i,n)} Ans=n1∑i=1nngcd(i,n)
我们发现 g c d ( i , n ) gcd(i,n) gcd(i,n) 只有 O ( n ) O(\sqrt{n}) O(n)种取值
可以枚举 g c d ( i , n ) gcd(i,n) gcd(i,n)
A n s = 1 n ∑ d ∣ n ∑ i = 1 n d [ g c d ( i , n d ) = 1 ] n d Ans=\frac{1}{n}\sum_{d|n}\sum_{i=1}^{\frac{n}{d}}[gcd(i,\frac{n}{d})=1]n^d Ans=n1∑d∣n∑i=1dn[gcd(i,dn)=1]nd
不就是欧拉函数吗
A n s = 1 n ∑ d ∣ n ϕ ( n d ) n d = ∑ d ∣ n ϕ ( n d ) n d − 1 Ans=\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
然后是暴力枚举 d d d,暴力算 ϕ \phi ϕ,复杂度 O ( n 3 4 ) O(n^{\frac{3}{4}}) O(n43)
并不会证明
#include
#include
#include
#include
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;
}