P6276 [USACO20OPEN] Exercise P 题解
Statement
求所有长度为 \(n\) 的排列的所有置换环的长度的最小公倍数的乘积。
\(n\le 7500\)。
Solution
显然有:
\[\text{答案}=\prod_{p^k, p\in \operatorname{prime}}p^{\text{存在一个置换环的长度为} p^k \text{的倍数的排列的个数}}
\]
枚举 \(p^k\),记 \(x=p^k\),考虑怎么计算 “存在一个置换环的长度为 \(p^k\) 的倍数的排列的个数”。发现直接求不好求,正难则反,考虑求其补集。
记 \(f_n\) 表示 “不存在一个置换环的长度为 \(p^k\) 的倍数的排列的个数”。发现还是不好求,于是再做一次减法。记 \(g_n\) 表示 “所有置换环的长度为 \(p^k\) 的倍数且长度为 \(n\) 的排列的个数”。易得:
\[g_n=\sum_{x\le i\le n,x|i}{n-1\choose i-1}(i-1)!g_{n-i}
\]
\[f_n=n!-\sum_{x\le i\le n,x|i}{n\choose i}g_i\times f_{n-i}
\]
那么对答案的贡献就是 \(\times p^{n!-f_n}\)。
发现 \(g_n\) 和 \(f_n\) 都只有 \(\lfloor\frac{n}{x}\rfloor\) 个的值不为 \(0\),总时间复杂度 \(\le O(\sum_{x=1}^n\frac{n^2}{x^2})=O(n^2)\)。
参考代码:
#include<bits/stdc++.h>
#define ll long long
#define mxn 7503
#define rep(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
int n,t,md,md1,d[mxn],p[mxn],f[mxn],g[mxn],c[mxn][mxn];
ll ans,fac[mxn];
int power(int x,int y){
int ans=1;
for(;y;y>>=1){
if(y&1)ans=(ll)ans*x%md;
x=(ll)x*x%md;
}
return ans;
}
signed main(){
cin>>n>>md,md1=md-1;
fac[0]=1;
rep(i,1,n)fac[i]=fac[i-1]*i%md1;
c[0][0]=1;
rep(i,1,n){
c[i][0]=1;
rep(j,1,i)c[i][j]=(c[i-1][j-1]+c[i-1][j])%md1;
}
rep(i,2,n){
if(!d[i])d[i]=p[++t]=i;
rep(j,1,t){
if(p[j]>d[i]||p[j]>n/i)break;
d[i*p[j]]=p[j];
}
}
ans=1;
rep(i,2,n)if(d[i]==i){
int x=i;
while(1){
g[0]=1;
for(int i=x;i<=n;i+=x){
g[i]=0;
for(int j=x;j<=i;j+=x){
g[i]=(g[i]+g[i-j]*fac[j-1]%md1*c[i-1][j-1])%md1;
}
}
f[0]=1;
rep(i,1,n)if((n-i)%x==0){
f[i]=fac[i];
for(int j=x;j<=i;j+=x)f[i]=(f[i]-(ll)f[i-j]*g[j]%md1*c[i][j])%md1;
}
ans=ans*power(i,(fac[n]-f[n]+md1)%md1)%md;
if((ll)x*i>n)break;
x*=i;
}
}
cout<<ans;
return 0;
}