【CF715E】Complete the Permutations(容斥,第一类斯特林数)

【CF715E】Complete the Permutations(容斥,第一类斯特林数)

题面

CF
洛谷
给定两个排列\(p,q\),但是其中有些位置未知,用\(0\)表示。
现在让你补全两个排列,定义两个排列\(p,q\)之间的距离为每次选择\(p\)中两个元素交换,使其变成\(q\)的最小次数。
求距离恰好为\([0,n-1]\)的填数方案数。

加强的题目在\(BZOJ\)上有,戳这里

题解

看到这道题目就觉得无比熟悉。回头翻了翻发现果然是省队集训的时候的题目。。。
果然都是原题啊。。。
如果排列已知,发现交换的最小次数显然就是沿着置换交换,交换次数为\(n-\)置换个数。那么考虑从\(p_i\)连边连向\(q_i\),那么要求的就是环的个数。
显然成链的中间的\(x-x\)边全部可以直接丢掉,那么只需要考虑最终的开头和结尾就知道这条链到底是什么类型的了。
那么边有四种:已知-已知,已知-\(0\)\(0\)-已知,\(0\)-\(0\)
对于已经成环的部分,我们显然不需要再考虑的。那么我们要做的就是把已经存在的链给合并成环,还要凭空用\(0-0\)边构造出一些环。
设有\(k\)条链,\(x-0\)\(0-x\)是分开考虑的,设\(g_i\)表示至少有\(i\)个环。
\[g_x=\sum_{i=x}^k{k\choose i}\begin{bmatrix}i\\x\end{bmatrix}(m+k-i)^{\underline{k-i}}\]
其中\(m\)\(0-0\)边的数量。
这里拿\(x-0\)边举例。首先选择若干个\(x-0\)边出来拼成\(x\)个环,枚举使用的边的个数组合数计算方案,然后把他们拼成环,环排列个数即第一类斯特林数。多出来的边随意拼接,显然他们需要找一个后继,即把\(0\)位置和另外一条边给拼起来,无论是选择另外一个\(0-0\)边来拼或者选择一个\(x-0\)边来拼都是可行的,因为每条边也只会有一个前驱,所以选择方案数是下降幂。\(0-x\)边是同理的。
\(f_x\)\(0-x\)边计算出来的恰好的结果,\(g_x\)\(x-0\)边计算出来的恰好的结果。两者卷积后,得到的是恰好形成了\(x\)环的结果。但是此时还多出了一些\(0-0\)链,它们形成环的方案数还是斯特林数。因此再将卷积的结果和斯特林数卷积。
最终因为\(0-0\)之间无顺序关系,所以还要乘上一个阶乘。

#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 550
#define MOD 998244353
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int n,a[MAX],b[MAX],deg[MAX];
int f[MAX],g[MAX],h[MAX],ans[MAX];
int nt[MAX],cnt[4];
bool vis[MAX<<1];
int C[MAX][MAX],A[MAX][MAX],S[MAX][MAX];
void dfs(int u,int ff)
{
    vis[u]=true;
    if(nt[u])
    {
        if(!vis[nt[u]])dfs(nt[u],ff);
        else cnt[2]+=1;
    }
    else
    {
        if(u>n&&ff>n)cnt[3]+=1;
        else if(u<=n&&ff>n)cnt[0]+=1;
        else if(u>n&&ff<=n)cnt[1]+=1;
    }
}
void calc(int *f,int n)
{
    for(int i=0;i<=n;++i)
        for(int j=i;j<=n;++j)
            add(f[i],1ll*C[n][j]*S[j][i]%MOD*A[n-j+cnt[3]][n-j]%MOD);
    for(int i=0;i<=n;++i)
    {
        int t=0;
        for(int j=i,d=1;j<=n;++j,d=MOD-d)
            add(t,1ll*d*f[j]%MOD*C[j][i]%MOD);
        f[i]=t;
    }
}
int main()
{
    n=read();
    for(int i=1;i<=n;++i)a[i]=read();
    for(int i=1;i<=n;++i)b[i]=read();
    for(int i=1;i<=n+n;++i)vis[i]=true;
    for(int i=1;i<=n;++i)
    {
        if(!a[i])a[i]=i+n;if(!b[i])b[i]=i+n;
        vis[a[i]]=vis[b[i]]=false;
        if(a[i]<=n||b[i]<=n)nt[a[i]]=b[i],++deg[b[i]];
    }
    for(int i=1;i<=n+n;++i)if(!vis[i]&&!deg[i])dfs(i,i);
    for(int i=1;i<=n+n;++i)if(!vis[i])dfs(i,i);
    C[0][0]=S[0][0]=A[0][0]=1;
    for(int i=1;i<=n;++i)
    {
        A[i][0]=C[i][0]=1;
        for(int j=1;j<=i;++j)
        {
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
            A[i][j]=1ll*A[i][j-1]*(i-j+1)%MOD;
            S[i][j]=(S[i-1][j-1]+1ll*(i-1)*S[i-1][j])%MOD;
        }
    }
    calc(f,cnt[0]);calc(g,cnt[1]);
    for(int i=0;i<=n;++i)
        for(int j=0;j<=i;++j)
            add(h[i],1ll*f[j]*g[i-j]%MOD);
    for(int i=0;i<=n;++i)
        for(int j=0;j<=i;++j)
            add(ans[i],1ll*h[j]*S[cnt[3]][i-j]%MOD);
    for(int i=0;i<=n;++i)ans[i]=1ll*ans[i]*A[cnt[3]][cnt[3]]%MOD;
    for(int i=0;i<n;++i)printf("%d ",n-i-cnt[2]>=0?ans[n-i-cnt[2]]:0);
    puts("");return 0;
}

转载于:https://www.cnblogs.com/cjyyb/p/10149652.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值