P4778 Counting swaps 题解

前言

  • 这这这太难了喵

\(solve\)

  • 要求在最小交换次数的限制下可以将序列通过交换进行排序的方案数
  • 先手玩一下样例,woc毫无规律
  • 想想办法
  • 欸,图论建模
  • 我们想到每个点都有一个目标位置,将现在位置和目标位置连边,这样会形成若干个环,题意转化成将这张图通过交换点来使其产生 \(n\) 个自环
  • 我们可以先想图中只有一个大环的情况至少需要的步数

对于一个点数为 \(n\) 的环,至少通过 \(n-1\) 次操作,才能使其呈现 \(n\) 个自环

  • 这个结论可以手模数据算,不过我们简单证一下
  • 需要一个结论

对于一个有 \(n\) 个节点的环,任选两个点交换,会产生有 \(x\) 个节点数和 \(y\) 个节点数的两个环,其中 \(x+y=n\)

  • woc这玩意不好证,用 \(csacademy\) 画画看看
  • 对于拆环的方案数,总共是 \(n\) 个,但是当 \(x = y\) 时会出现重复的情况,个数为 \(n \over 2\),所以当 \(x = y\) 时方案数为 \(n \over 2\)
  • 我们设 \(k_i\) 表示一个大小为 \(i\) 的环在最小操作数的限制下使序列合法的方案数
  • 拆环,拆成节点数分别为 \(x\)\(y\) 的环,由于 \(x + y = n\),所以 \(y = n-x\),由于要求有序,所以对于一个环 \(x\) 来说,其需要 \(x-1\) 次操作才能使其合法,\(y\) 同理,方案数为 \((x - 1 + y - 1 = n - 2)!\)\(x\) 集合由于随意换,所以方案数为 \((x-1)!\)\(y\) 同理
  • 方便表示,设 \(g_{x,y}\) 表示拆成节点数为 \(x\)\(y\) 的方案数,所以我们就有 \(k\) 的递推式

\[k_i = \sum\limits_{j=1}^{\lfloor {j \over 2} \rfloor}{k_j \times k_{i-j} \times g_{j,i-j} \times {{(i-2)!} \over {(j-1)! \times (i-j-1)!}}} \]

  • \(g_{j,i-j}\) 用代码表示为 (j == i-j ? i/2 : i)
点击查看代码
for (int i=1;i<=n;i++)
    for (int j=1;j<=i/2;j++)
        (k[i]+=k[j]*k[i-j]%p*(j == i-j ? i/2 : i)%p*f[i-2]%p*g[j-1]%p*g[i-j-1]%p)%=p;
  • 这只是我们讨论的一个环的情况,但是图中可能有多个环,所以我们要把这些方案数累乘起来,设有 \(m\) 个环,设每个环大小为 \(cnt\),由于这些环的操作相互独立,操作数为一个多重集全排列,一个多重集全排列问题,方案数为总数阶乘比上所有数出现次数的乘积,即设共出现 \(n\) 次,有 \(k\) 个数,第 \(i\) 个数出现了 \(n_i\) 次,有:\(n = n_1+n_2+\dots+n_k\),所以方案数为:$ {n!} \over {n_1! \times n_2! \times \dots \times n_k!}$
  • 所以我们有答案式:

\[\prod\limits_{i=1}^{m}{k_{cnt_i}} \times {{(n-k)!} \over {\prod\limits_{i=1}^{m}{(cnt_i-1)!}}} \]

  • 请注意,乘号两边需要分开计算,而不是一个大连乘
点击查看代码
int num=1,ans=1;
for (int i=1;i<=ot;i++)
    (num*=f[cnt[i]-1])%=p,(ans*=k[cnt[i]])%=p;
(ans*=f[n-ot]*inv(num)%p)%=p;
  • 代码里 \(ot\) 即为 \(m\)
  • 但是你发现这个 \(k\) 的预处理是 \(n^2\) 的,就算你提前预处理也不行
  • 所以想想办法将这个预处理复杂度降下来
  • 没啥法,打个表看看
  • 1 1 3 16 125 1296 16807 262144 4782969 100000000 357947673
  • woc这是啥
  • 欸,我们有 \(OEIS\)

2026-09-05 10-12-06屏幕截图

  • 欸,\(k_i = i^{i-2}\)
  • 那就行了,甚至不用预处理,直接现求,横竖带老哥
  • 就完了
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
constexpr int maxn=1e5+10,p=1e9+9;
int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0' || ch>'9')
    {
        if (ch == '-') f=-1;
        ch=getchar();
    }
    while (ch>='0' && ch<='9')
    {
        x=(x<<1)+(x<<3)+ch-'0';
        ch=getchar();
    }
    return x*f;
}
int n;
int h[maxn],to[maxn],nxt[maxn],tot;
void addedge(int x,int y)
{
    tot++;
    to[tot]=y;
    nxt[tot]=h[x];
    h[x]=tot;
}
int cnt[maxn],ot;
int v[maxn];
void dfs(int x)
{
    for (int i=h[x];i;i=nxt[i])
    {
        int y=to[i];
        if (v[y]) return;
        // cerr << x << "->" << y << endl;
        v[y]=1;
        cnt[ot]++;
        dfs(y);
    }
}
int power(int x,int y)
{
    if (y == -1) return 1;
    int res=1;
    while (y)
    {
        if (y&1) (res*=x)%=p;
        (x*=x)%=p;
        y>>=1;
    }
    return res;
}
int inv(int x) {return power(x,p-2);}
int f[maxn],g[maxn];
void init(int x)
{
    f[0]=g[0]=1;
    for (int i=1;i<=x;i++)
        f[i]=f[i-1]*i%p,g[i]=inv(f[i])%p;
}
signed main()
{
    init(maxn-10);
    int T=read();
    while (T--)
    {
        tot=ot=0;
        memset(h,0,sizeof(h));
        memset(cnt,0,sizeof(cnt));
        memset(v,0,sizeof(v));
        n=read();
        for (int i=1;i<=n;i++)
        {
            int x=read();
            addedge(x,i);
            // cerr << x << " to " << i << endl;
        }
        for (int i=1;i<=n;i++)
        {
            if (v[i]) continue;
            cnt[++ot]++;
            v[i]=1;
            dfs(i);
        }
        // for (int i=1;i<=ot;i++)
        //     cerr << cnt[i] << " ";
        // cerr << endl;
        int num=1,ans=1;
        for (int i=1;i<=ot;i++)
            (num*=f[cnt[i]-1])%=p,(ans*=power(cnt[i],cnt[i]-2))%=p;
        (ans*=f[n-ot]*inv(num)%p)%=p;
        printf("%lld\n",ans);
    }
    return 0;
}
posted @ 2026-09-05 10:18  msjing  阅读(13)  评论(0)    收藏  举报