【CF398E】Sorting Permutations(sort)

Description

我们有一个是1到n 的排列的序列a1, a2,… ,an。在一秒钟里,我们可以选择一些不相交的数对(u1,v1),(u2,v2),… (uk,vk)并交换所有a_ui 和a_vi,同时对于所有的i 有1<=ui < vi<=n。数对们不相交当且仅当所有的ui 和vi 不相同。

我们想要尽快地把序列重新排列成递增顺序。给定初始排列,计算完成排序的方法数。两种方法不同当且仅当有一个时间t,此时用于交换的数对集合不相同(数对的顺序不重要)。如果给定的排列已经排好序,那么排序所需时间为零,排序方法唯一。

为了让这个问题更有趣,我们在排列上打k个洞。因此,在a1,a2,…,an 中恰好有k个数没有被确定。对于填充这些洞的所有可能情况,计算方法数,然后输出这些值的和模1000000007(10^9 + 7)。

Input

第一行包含两个整数n(1<=n <=10^5)和k(0<=k<=12)。第二行为排列序列a1,…,an(0<=ai<=n)。如果一个数没有被确定,那么它被标记为0。序列有恰好k个0。对于其他不等于零的ai 来说,它们之间两两不相同。

Output

输出方法数之和模1000000007(10^9 + 7)。

Sample Input

输入1:

5 0

1 5 2 4 3

输入2:

5 2

1 0 2 4 0

Sample Output

输出1:

6

输出2:

7

Data Constraint

对于30% 的数据,n<=10。

对于另外30% 的数据,k = 0。

这题。。无力吐槽了,不知道出出来的意义何在。。锻炼思维??

首先。。我们可以证(猜)明(想),从给出状态转移到目标状态最多只需要两步。(不要问我怎么证明,出题人都不证,真是有毒)

知道这个结论之后就是xjb乱搞了。。
首先我们连边,从当前状态连向目标状态,注意是单向。
然后我们肯定会得到环(除非不用步数),那么我们有两种情况,一种是环相交,一种是环不相交。不相交的话,方案数肯定是其中一个环的大小(又是蜜汁结论)。
所以假设现在没有洞,我们的做法大概可以概括如下:

将排列分解成不相交的环。设有c个环,长度都是l。我们设a[c,l]是完全对长度l的c个环进行排序的方法的数量。那么答案就是这样,这是因为只有相同长度的环相互作用(又是蜜汁结论)。

那么我们可以得出一个求解a的公式:
a[c, l] = l·a[c - 1, l] + l·(c - 1)·a[c - 2, l]

那么答案就应该是所有l上的a乘起来。
可是问题是现在还神TM多出来k,这个我们既可以直接暴力枚举,12!可过,也可以dp,考虑所有可能的方式来分割最多12个路径。这时候我们只需将一些常量乘以每个分区,并将其求和就可以了。这样就可以成功减少复杂度。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define pb push_back 
using namespace std;
int n,m;
const int N=1e5+5;
const int mo=1e9+7;
typedef long long ll;
int k,p[N],bz[N],vis[N];
int chain[N],cir[N],cir1[N],ans1;
int top,len[N],num[N],ans;
vector<int> a[N],inv[N];
inline int pow(int a,int b)
{
    int r=1;
    while (b)
    {
        if (b&1)r=(ll)r*a%mo;
        a=(ll)a*a%mo;
        b>>=1;
    }
    return r;
}
inline void pre()
{
    fo(i,1,n)
    {
        a[i].pb(1);
        inv[i].pb(1);
        for(int j=1;i*j<=n;j++)
        { 
            int tmp=(ll)a[i][j-1]*i%mo;
            if(j>=2)tmp=(tmp+(ll)a[i][j-2]*(j-1)%mo*i%mo)%mo;
            a[i].pb(tmp);
            tmp=pow(tmp,mo-2);
            inv[i].pb(tmp);
        }
    }
}
inline void dp()
{
    ans1=1;
    fo(i,1,n)
    ans1=(ll)ans1*a[i][cir[i]]%mo;
}
inline void calc(int d)
{
    int tmp=ans1;
    fo(i,1,top)
    {
        int l=len[i];
        tmp=(ll)tmp*inv[l][cir[l]]%mo;
        cir[l]++,cir1[min(l,3)]++;
        tmp=(ll)tmp*a[l][cir[l]]%mo;
    }
    if (cir1[3])d=(ll)d*tmp%mo;
    ans=(ans+d)%mo;
    fo(i,1,top)
    {
        int l=len[i];
        cir[l]--,cir1[min(l,3)]--;
    }
}
inline void dfs(int now,int d)
{
    if (now==k)
    {
        calc(d);
        return;
    }
    len[++top]=chain[now];
    num[top]=1;
    dfs(now+1,d);
    num[top--]=0;
    fo(i,1,top)
    {
        len[i]+=chain[now];
        num[i]++;
        dfs(now+1,(ll)d*(num[i]-1)%mo);
        num[i]--;
        len[i]-=chain[now];
    }
}
inline void solve()
{
    pre();
    int tot=0;
    fo(i,1,n)
    if (!bz[i])
    {
        int x=i,y=0;
        while (x)
        {
            vis[x]=1;
            x=p[x];
            ++y;
        }
        chain[tot]=y;
        tot++;
    }
    fo(i,1,n)
    if(!vis[i])
    {
        int x=i,y=1;
        while (x)
        {
            vis[x]=1;
            if (p[x]==i)break;
            x=p[x];
            y++;
        }
        ++cir[y];
        ++cir1[min(y,3)];
    }
    dp();
    dfs(0,1);

}
int main()
{
    freopen("sort.in","r",stdin);
    freopen("sort.out","w",stdout);
    scanf("%d%d",&n,&k);
    fo(i,1,n)
    {
        scanf("%d",&p[i]);
        bz[p[i]]=1;
    }
    solve();
    printf("%d\n",ans);
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值