洛谷4859 BZOJ3622 已经没什么好害怕的了(DP,二项式反演)

题目链接:

洛谷

BZOJ

题目大意:有两个长为 $n$ 的序列 $a,b$,问有多少种重排 $b$ 的方式,使得满足 $a_i>b_i$ 的 $i$ 的个数比满足 $a_i<b_i$ 的 $i$ 的个数恰好多 $k$ 个。答案对 $10^9+9$ 取模。

$1\le n\le 2000,0\le k\le n$。保证 $a,b$ 中没有相同的数。


 首先根据小学数学知识可知,$a_i>b_i$ 的个数应该是 $\frac{n+k}{2}$。如果 $n+k$ 不是偶数那么就无解。

那么就可以DP了。首先将 $a$ 和 $b$ 分别排序,令 $c_i$ 表示 $b$ 中 $<a_i$ 的数的个数。

设 $dp_{i,j}$ 表示在前 $i$ 个 $a$ 中,选了 $j$ 个 $a$ 和 $j$ 个 $b$ 并凑出了恰好 $j$ 对满足 $a>b$ 的对的方案数,那么有:

$dp_{i,0}=1$

$dp_{i,j}=dp_{i-1,j}+dp_{i-1,j-1}(c_i-j+1)$

解释一下第二句,前半部分是表示 $a_i$ 不找,后半部分表示 $a_i$ 找。本来可以有 $c_i$ 个 $b$ 可以选,但是前面 $j-1$ 个 $i$ 已经选了 $j-1$ 个了。又因为 $a$ 从小到大,所以还可以选 $c_i-j+1$ 个。

令 $f_k$ 为恰好 $k$ 对数的答案, $g_k$ 为至少 $k$ 对数的答案。

我们发现 $g_k$ 比较好算,$g_k=dp_{n,k}(n-k)!$。因为选出了 $k$ 对之后,剩下的可以随便搭配。

然后又可以发现 $g_k=\sum\limits^n_{i=k}{i\choose k}f_i$。为什么???因为这题标签是二项式反演啊……(smg……)

好吧,我不会证,直接用吧(逃

那么 $f_k=\sum\limits^n_{i=k}(-1)^{i-k}{i\choose k}g_i$。

时间复杂度 $O(n^2)$。


upd:问了PBdalao为什么是组合数,把他的话放这吧:

对于 $j$ 的一个方案,它在 $i$ 中必然是这么统计的:有 $i$ 个是DP得到的,另外 $j-i$ 个是后面乱搞得到的。

所以 $j$ 中每一组 $i$ 都被统计了一次,最后就被统计了 $j\choose i$ 次。

(很有道理,不是吗?)


 

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=2222,mod=1000000009;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline int read(){
    char ch=getchar();int x=0,f=0;
    while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    return f?-x:x;
}
int n,k,a[maxn],b[maxn],c[maxn],dp[maxn][maxn],fac[maxn],inv[maxn],invfac[maxn];
inline int C(int n,int m){
    return 1ll*fac[n]*invfac[m]%mod*invfac[n-m]%mod;
}
inline int g(int x){
    return 1ll*dp[n][x]*fac[n-x]%mod;
}
inline int f(int x){
    int ans=0;
    FOR(i,x,n){
        int v=1ll*C(i,x)*g(i)%mod;
        if((i-x)&1) ans=(ans-v+mod)%mod;
        else ans=(ans+v)%mod;
    }
    return ans;
}
int main(){
    n=read();k=read();
    if((n+k)&1) return puts("0"),0;
    k=(n+k)>>1;
    fac[0]=fac[1]=inv[1]=invfac[0]=invfac[1]=1;
    FOR(i,2,n){
        fac[i]=1ll*fac[i-1]*i%mod;
        inv[i]=mod-1ll*(mod/i)*inv[mod%i]%mod;
        invfac[i]=1ll*invfac[i-1]*inv[i]%mod;
    }
    FOR(i,1,n) a[i]=read();
    FOR(i,1,n) b[i]=read();
    sort(a+1,a+n+1);sort(b+1,b+n+1);
    int cur=1;
    FOR(i,1,n){
        while(cur<=n && b[cur]<a[i]) cur++;
        c[i]=cur-1;
    }
    dp[0][0]=1;
    FOR(i,1,n){
        dp[i][0]=dp[i-1][0];
        FOR(j,1,i) dp[i][j]=(dp[i-1][j]+1ll*dp[i-1][j-1]*(c[i]-j+1))%mod;
    }
    printf("%d\n",f(k));
}
二项式反演

 

posted @ 2019-02-06 21:47  ATS_nantf  阅读(333)  评论(0编辑  收藏  举报