P4859 已经没有什么好害怕的了

题意

\(a\)\(b\) 两个长度为 \(n\) 的序列,其中元素两两配对, \(a>b\) 的配对需比 \(a<b\) 的配对多恰好 \(k\) 个,求方案数

Solution

我们设 \(f_{i,j}\) 为前 \(i\)\(a\) 中,选了 \(j\)\(a>b\) 的方案数

可得状态转移方程:

\[f_{i,j}=f_{i-1,j}+f_{i-1,j-1}*(l_i-j+1) \]

\(l_i\) 表示离 \(a_i\) 最近的一个 \(b\)

再设 \(g_i\)\(\geq i\) 个的配对方案数, \(h_i\) 为恰好 \(=i\) 个的方案数,可得 \(g_i=f_{n,i}*(n-i)!\)

且容易得, \(f_i\)\(g_j\) 中算了 \((^i_j)\)

所以,得到:

\[g(k)=\sum\limits_{i=k}^n\dbinom ikf(i) \]

由二项式反演,得:

\[f(k)=\sum\limits_{i=k}^n(-1)^{i-k}\dbinom ikg(i) \]

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#define ll long long

using namespace std;
const int N=2005,mod=1e9+9;
int n,k,a[N],b[N];
ll f[N][N],fac[N],inv[N],fac_inv[N];
int g[N];

inline int read(){
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
    return x*f;
}

inline void init(){
    inv[1]=1;fac[0]=fac_inv[0]=1;
    for(int i=1;i<=n;i++){
        if(i!=1) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
        fac[i]=fac[i-1]*i%mod;
        fac_inv[i]=fac_inv[i-1]*inv[i]%mod;
    }
}

inline ll C(int n,int m){
    return fac[n]*fac_inv[m]%mod*fac_inv[n-m]%mod;
}

int main(){
    n=read();k=read();
    if((n+k)&1){
        puts("0");
        return 0;
    }
    k+=(n-k)/2;
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<=n;i++) b[i]=read();
    sort(a+1,a+n+1);
    sort(b+1,b+n+1);
    init();
    int now=0;
    for(int i=1;i<=n;i++){
        while(now<n&&a[i]>b[now+1]) now++;
        g[i]=now;
    }
    for(int i=0;i<=n;i++) f[i][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=g[i];j++)
            f[i][j]=(f[i-1][j]+f[i-1][j-1]*(g[i]-j+1)%mod)%mod;
    ll ans=0;
    for(int i=k;i<=n;i++)
        ans=(ans+(((i-k)&1)?-1:1)*fac[n-i]*f[n][i]%mod*C(i,k)%mod+mod)%mod;
    printf("%lld\n",ans);
    return 0;
}
posted @ 2020-09-09 21:14  jasony_sam  阅读(102)  评论(0编辑  收藏  举报