雅礼集训6.25 : gift (DP)

题意:
这里写图片描述

(n2000) ( n ≤ 2000 )

题解:
好神啊。

首先考虑如果序列已知怎么做,我们把 (ai,bi) ( a i , b i ) 看做图中的边,那么形成若干环,答案就是 n n − 环 数

考虑如果不已知,那么已经有的边肯定形成若干条链。 我们先把已经有的环丢掉,再把形如:

(0a1a1a2a2......anan0) ( 0 a 1 a 2 . . . a n a 1 a 2 . . . a n 0 )

丢掉,因为相当于接在了一个环的固定位置而已。

现在只有这三种情况:
1.

(0a1a1a2a2......an) ( 0 a 1 a 2 . . . a 1 a 2 . . . a n )

2.
(a1a2a2......anan0) ( a 1 a 2 . . . a n a 2 . . . a n 0 )

3.
(a1a2a2......an) ( a 1 a 2 . . . a 2 . . . a n )

我们把他们分为 0x 0 − x 链, x0 x − 0 链, 以及 00 0 − 0 链。现在要求把他们接在一起形成 i i 个环的方案数。
首先,如果0x,x0要接起来,那么必须经过 00 0 − 0 链。
我们记 fi f i 0x 0 − x 内部形成 i i 个环的方案数,gi x0 x − 0 内部形成 i i 个环的方案数。其他没在环内部的0x链一定有 00,0x 0 − 0 , 0 − x 后继, x0 x − 0 一定有 00,x0 0 − 0 , x − 0 前驱。

考虑如何求 fi f i ,我们先求至少 i i 个,再二项式反演即可。设n 0x 0 − x 链数量, m m 00链的数量。 求至少的公式:
fi=nj=i(nj) f i ′ = ∑ j = i n ( n j ) {ji} { j i } Am+ni,ni A m + n − i , n − i

最后的排列数相当于给每个链上的 0x 0 − x 分配后继。

然后通过 fi,gi f i , g i 的卷积,我们就得到了 hi h i 表示环全为 0x 0 − x x0 x − 0 内部形成,其他为 00 0 − 0 链的数量,然后就可以很方便的计算总方案数了。

最后给每个未知标号的边分配标号即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const int RLEN=1<<18|1;
inline char nc() {
    static char ibuf[RLEN],*ib,*ob;
    (ib==ob) && (ob=(ib=ibuf)+fread(ibuf,1,RLEN,stdin));
    return (ib==ob) ? -1 : *ib++;
}
inline int rd() {
    char ch=nc(); int i=0,f=1;
    while(!isdigit(ch)) {if(ch=='-')f=-1; ch=nc();}
    while(isdigit(ch)) {i=(i<<1)+(i<<3)+ch-'0'; ch=nc();}
    return i*f;
}

const int N=2e3+50,mod=998244353;
inline int add(int x,int y) {return (x+y>=mod) ? (x+y-mod) : (x+y);}
inline int dec(int x,int y) {return (x-y<0) ? (x-y+mod) : (x-y);}
inline int mul(int x,int y) {return (LL)x*y%mod;}
inline int power(int a,int b,int rs=1) {for(;b;b>>=1,a=mul(a,a)) if(b&1) rs=mul(rs,a); return rs;}
int n,a[N],b[N],c[N],vis[N],in1[N],in2[N];
int A[N][N],S[N][N],C[N][N],f[N],g[N],h[N],deg[N];
vector <int> edge[N];
inline void init() {
    for(int i=0;i<=n;i++) S[i][i]=1, C[i][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<i;j++)
            S[i][j]=add(S[i-1][j-1],mul(S[i-1][j],i-1));
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i;j++)
            C[i][j]=add(C[i-1][j-1],C[i-1][j]);
    for(int i=0;i<=n;i++) {
        A[i][0]=1;
        for(int j=1;j<=i;j++)
            A[i][j]=mul(A[i][j-1],i-j+1);
    }
}
inline int dfs(int bg,int ed) {
    vis[ed]=1;
    if(!edge[ed].size() || vis[edge[ed][0]]) return ed;
    return dfs(bg,edge[ed][0]);
}
inline void rec(int *ff,int nn) {
    for(int i=0;i<=nn;i++)
        for(int j=i+1;j<=nn;j++) {
            if((j-i)&1) ff[i]=dec(ff[i],mul(C[j][i],ff[j]));
            else ff[i]=add(ff[i],mul(C[j][i],ff[j]));
        }
}
inline void calc(int *ff,int nn) {
    for(int i=0;i<=nn;i++) 
        for(int j=i;j<=nn;j++)
            ff[i]=add(ff[i],mul(mul(C[nn][j],S[j][i]),A[c[3]+nn-j][nn-j]));
    rec(ff,nn);
}
int main() {
    n=rd(); init();
    for(int i=1;i<=n;i++) in1[a[i]=rd()]=1;
    for(int i=1;i<=n;i++) in2[b[i]=rd()]=1;
    for(int i=1;i<=n;i++) 
        if(a[i] && b[i]) edge[a[i]].push_back(b[i]), ++deg[b[i]];
    int tot=n;
    for(int i=1;i<=n;i++) 
        if(!deg[i]) {
            int tl=dfs(i,i);
            if(in2[i] && in1[tl]) ++c[4], --tot;
            else if(in1[tl]) ++c[1];
            else if(in2[i]) ++c[2];
            else ++c[3];
        }
    for(int i=1;i<=n;i++)
        if(!vis[i]) ++c[0], dfs(i,i);
    n=tot;
    calc(f,c[1]); calc(g,c[2]); 
    for(int i=0;i<=n;i++) if(f[i])
        for(int j=0;j<=n;j++) if(g[j])
            h[i+j]=add(h[i+j],mul(f[i],g[j]));
    memset(f,0,sizeof(f));
    for(int i=0;i<=n;i++) if(h[i])
        for(int j=0;j<=c[3];++j) 
            f[i+j]=add(f[i+j],mul(h[i],S[c[3]][j]));
    for(int i=0;i<=n;i++) f[i]=mul(f[i],A[c[3]][c[3]]);
    for(int i=0;i<c[4];i++) cout<<0<<' ';
    for(int i=0;i<n;i++)
        cout<<f[n-i-c[0]]<<' ';
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值