洛谷P4778 计数交换 Counting swaps

原题链接

洛谷 P4778
AcWing 212

题目分析

虽然要求的是方案数,但是最好还是先把至少需要几步弄好了再说。题目中每一个点都要到另一个点,不妨给这两个点连一条边。现在我给出一下样例: 6 : 2   1   3   5   6   4 6:2 1 3 5 6 4 6:2 1 3 5 6 4我们会得到如下图:在这里插入图片描述
请注意,其中结点 3 3 3是自环。不难发现,每一个连通块都一定是一个简单环,因为每个点都只有一个出度、一个入度。我们的目标就是通过改变一些边使得原图变成 n n n个自环。
所以原问题就是分裂这些环,设环大小为 a a a,所以每个环至少需要 a − 1 a-1 a1步分裂成自环。

尝试在图中操作

现在我们来看一看某一个环中交换两个点会发生什么:
在这里插入图片描述
上图对应序列 2   3   4   5   1 2 3 4 5 1 2 3 4 5 1。接下来我们更换位置 2 2 2和位置 3 3 3(序列 2   4   3   5   1 2 4 3 5 1 2 4 3 5 1):在这里插入图片描述
发现刚好把原图分成了两个环,让剩余的步数更少了。现在我们再看一看交换两个环上的点会发生什么:在这里插入图片描述
原图对应序列 3   1   2   5   6   4 3 1 2 5 6 4 3 1 2 5 6 4现在我们交换位置 1 1 1 4 4 4(序列 5   1   2   3   6   4 5 1 2 3 6 4 5 1 2 3 6 4):在这里插入图片描述
发现我们把两个环变成了一个环,让需要交换的步数更多了!

性质的证明

我们刚刚画了图思考了一下,发现好像只有交换环内的点更优。那是不是一定如此呢?不难发现任意的序列都唯一对应一个只含简单环且没有环外边的图,任意的只含简单环且没有环外边的图都唯一对应一个序列。所以我们现在来分析图的性质,图的性质就是序列的性质。
如果交换了两个点 a a a b b b,其数值分别为 c c c d d d,那么就断掉了 a → b a\to b ab c → d c\to d cd的边,然后连接 a → d a\to d ad b → c b\to c bc。如果原来 a a a b b b在一个环内,就会断掉两条环上的边,然后就会形成了两条链,接着连接了两条边,不难发现这两条边连接的都是两条链的起点和终点,所以一定会分裂。如果 a a a b b b是两个环上的点,那就会分别断掉两个环,新连接的两条边就会连接成一个环。所以选择环内的任意两点交换都是可以的。

初步的dp

现在我们来考虑如何算出来这个方案数。首先我们先针对单个环处理。设 f i f_i fi表示大小为 i i i的环,其中 f 1 = 1 f_1=1 f1=1。一个环可以分成两个环,所以我们可以枚举这两个环的大小。然后选择两个点来分割,不难看出可以找到 n n n个分割的方案。但是请注意,如果分割的两部分大小一样就会出现重复,只有 n 2 \cfrac{n}{2} 2n个分割方案。设 T T T表示分割的方案数,则有状态转移方程: f i = ∑ x n 2 f x × f n − x × T f_i=\displaystyle\sum_x^{\frac{n}{2}}f_x\times f_{n-x}\times T fi=x2nfx×fnx×T但是请注意,这个状态转移方程并不对!我们分割出来的两个集合并没有先后顺序,所以可以两个集合的操作可以混乱得放在一起,只需要保证两个集合内部是有序的。因为一个集合处理至少需要 x − 1 x-1 x1步,所以 01 01 01串的长度为 n − 2 n-2 n2。现在我们先假设吉和内部也可以随意摆放,所以方案数就是 ( n − 2 ) ! (n-2)! (n2)!。然后单个集合内只能有序, 1 1 1种方案,但是第一个集合内随意摆放产生了 ( x − 1 ) ! (x-1)! (x1)!个方案,除法计算即可。另一个集合同理。得到最终的状态转移方程: f i = ∑ x = 1 n 2 f x × f n − x × T × ( n − 2 ) ! ( x − 1 ) ! ( n − x − 1 ) ! f_i=\displaystyle\sum_{x=1}^{\frac{n}{2}}f_x\times f_{n-x}\times T\times \cfrac{(n-2)!}{(x-1)!(n-x-1)!} fi=x=12nfx×fnx×T×(x1)!(nx1)!(n2)!现在思考一下答案是什么。因为整体会有很多的环,所以每个环都要乘起来。因为答案中两个集合的顺序也可以打乱,设有 k k k个环,每个环的大小为 l i l_i li,所以答案为: a n s = ∏ i = 1 k f l i × ( n − k ) ! ∏ j = 1 k [ ( l j − 1 ) ! ] ans=\displaystyle\prod_{i=1}^kf_{l_i}\times\cfrac{(n-k)!}{\displaystyle\prod_{j=1}^k[(l_j-1)!]} ans=i=1kfli×j=1k[(lj1)!](nk)!

超时的处理

但是以上的状态转移方程算 f f f Θ ( n 2 ) \Theta(n^2) Θ(n2)的,会超时。但是我们可以先看一看 f f f数组算出来有什么特点: 1   1   3   16   125   1296   16807   262144   4782969   100000000... 1 1 3 16 125 1296 16807 262144 4782969 100000000... 1 1 3 16 125 1296 16807 262144 4782969 100000000...发现了什么?你没发现?放到 O E I S OEIS OEIS上看看:在这里插入图片描述
O E I S OEIS OEIS告诉我们,这串数列是 n n − 2 n^{n-2} nn2。所以, f f f数组的处理直接变成了 Θ ( n log ⁡ n ) \Theta(n\log n) Θ(nlogn)

最后的问题:寻找环

因为本题只有简单环,直接 d f s dfs dfs就能很快地找到,时间复杂度只有 Θ ( n ) \Theta(n) Θ(n)。应该不会有人不会用 d f s dfs dfs找环吧?不会的详见代码

题目代码

#include<bits/stdc++.h>
using namespace std;
const int NN=1e5+4,P=1e9+9;
int f[NN],inv[NN],a[NN],l[NN];
bool vis[NN];
int qmi(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1)
            res=1ll*res*a%P;
        a=1ll*a*a%P;
        b>>=1;
    }
    return res;
}
int dfs(int u)
{
    vis[u]=true;
    if(vis[a[u]])
        return 1;
    return dfs(a[u])+1;
}
int main()
{
    inv[0]=f[1]=1;
    for(int i=1;i<NN;i++)
        inv[i]=1ll*inv[i-1]*i%P;
    for(int i=2;i<NN;i++)
        f[i]=qmi(i,i-2);
    int t;
    scanf("%d",&t);
    while(t--)
    {
        memset(vis,false,sizeof(vis));
        int n,cnt=0;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=1;i<=n;i++)
            if(!vis[i])
                l[++cnt]=dfs(i);
        int ans=inv[n-cnt];
        for(int i=1;i<=cnt;i++)
            ans=1ll*ans*f[l[i]]%P*qmi(max(1,inv[l[i]-1]),P-2)%P;
        printf("%d\n",ans);
    }
    return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值