洛谷 P4709 - 信息传递(置换+dp)
阅读原文时间:2023年07月08日阅读:2

题面传送门

一道挺有意思的题罢……

首先看到这种与置换乘法相关的题,首先把这些置换拆成一个个置换环,假设输入的置换有 \(m\) 个置换环,大小分别为 \(s_1,s_2,\cdots,s_m\),显然置换环与置换环的顺序是不影响的,因此我们只用考虑它的大小即可。

其次我们考虑对于一个置换,我们来考虑对其进行 \(k\) 次幂后会得到什么。手玩几组数据就可以发现,原来不在同一个置换环中的元素,进行 \(k\) 次幂后,肯定也不会在同一个置换环中,而有的原本在同一个置换环中的元素进行 \(k\) 次幂后会被拆开,具体来说,对于一个大小为 \(s\) 的置换环,进行 \(k\) 次幂后会拆成 \(\gcd(s,k)\) 个等大小的置换环,道理很明白,对于一个元素,你每次绕着置换环走 \(k\) 步,显然 \(\dfrac{s}{\gcd(s,k)}\) 次就会回到源点,因此 \(k\) 次幂后拆出的置换环中,单个置换环大小就是 \(\dfrac{s}{\gcd(s,k)}\)。

也就是说 \(s_1,s_2,\cdots,s_m\) 中有一些大小相同的置换环能拼起来,显然不同大小的置换环的方案数是独立的,因此我们可以分别求出每个大小的置换环拼起来的方案数再用乘法原理乘起来。考虑设 \(c_i\) 表示大小为 \(i\) 的置换环有多少个,那么根据之前的结论 \(j\) 个大小为 \(i\) 的置换环能够拼起来当且仅当 \(\gcd(ij,k)=j\)。那么怎么求大小为 \(i\) 的置换环拼起来有多少种方案呢?这时候就要用到 \(dp\) 了,我们设 \(dp_j\) 表示用了 \(j\) 个大小为 \(i\) 的置换环的方案数,考虑转移,我们枚举第 \(j\) 个大小为 \(i\) 的置换环跟多少个环在一起拼成大环,假设为 \(r\) 个,那么 \(dp_i=\sum\limits_rdp_{i-r}\dbinom{i-1}{r-1}f(r,i)[\gcd(ir,k)=r]\),其中 \(f(r,i)\) 表示将 \(r\) 个大小为 \(i\) 的置换环拼起来的方案数,注意到 \(\gcd(ir,k)=r\Rightarrow r\mid k\),因此枚举的 \(r\) 必须是 \(k\) 的约数,又 \(\sum c_i=m\) 是 \(\mathcal O(n)\) 级别的,因此这样暴力 DP 总枚举量是 \(n·d(k)\) 的。

最后考虑怎样求 \(f(r,i)\),由于最后拼成的是一个环,因此我们钦定第一个环的第一个元素必须在第一个位置,否则会算重,将剩余 \(r-1\) 个环填入剩余 \(r-1\) 个位置有 \((r-1)!\) 种,剩余 \((r-1)!\) 个环也可自由旋转,方案数为 \(i^{r-1}\),因此 \(f(r,i)=(r-1)!\times i^{r-1}\),随便算算即可。

时间复杂度 \(n·d(k)·\log n\),因为计算 \(f(r,i)\) 时涉及快速幂。

const int MAXN=1e5;
const int MOD=998244353;
int n,p[MAXN+5],c[MAXN+5],dp[MAXN+5],vis[MAXN+5];
int fac[MAXN+5],ifac[MAXN+5];vector<int> f;
int gcd(int x,int y){return (!y)?x:gcd(y,x%y);}
int qpow(int x,int e){
    int ret=1;
    for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
    return ret;
}
void init_fac(int n){
    for(int i=(fac[0]=ifac[0]=ifac[1]=1)+1;i<=n;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
    for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*ifac[i]%MOD;
}
int binom(int n,int k){return 1ll*fac[n]*ifac[k]%MOD*ifac[n-k]%MOD;}
int main(){
    scanf("%d",&n);init_fac(n);int ans=1;
    for(int i=1;i<=n;i++) scanf("%d",&p[i]);
    for(int i=1;i<=n;i++) if(n%i==0) f.pb(i);
    for(int i=1;i<=n;i++) if(!vis[i]){
        int siz=0;for(int j=i;!vis[j];j=p[j]) vis[j]=1,siz++;
        c[siz]++;
    }
    for(int i=1;i<=n;i++){
        dp[0]=1;
        for(int j=1;j<=c[i];j++){
            dp[j]=0;
            for(int k=0;k<f.size()&&f[k]<=j&&i*f[k]<=n;k++)
                if(gcd(i*f[k],n)==f[k]){
                    dp[j]=(dp[j]+1ll*dp[j-f[k]]*binom(j-1,f[k]-1)%MOD*
                    fac[f[k]-1]%MOD*qpow(i,f[k]-1))%MOD;
                }
        } ans=1ll*ans*dp[c[i]]%MOD;
    } printf("%d\n",ans);
    return 0;
}