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

题目描述:

bz

luogu

题解:

$dp$+二项式反演。

首先把两个集合排序,然后dp。$dp[i][j]$表示前$i$个糖果里面我让$j$个匹配比它小的药片,剩下不管的方案数。

容易得到方程为$dp[i][j]=dp[i-1][j]+dp[i-1][j-1]+(lim-(j-1))$,其中$lim$表示比它小的药片的数量。

这一部分是$O(n^2)$的。

发现他问的是$cnt(a>b)-cnt(a<b)==k$,那么$cnt(a>b)=(n+k)/2$。

dp之后我们得到了对于$n$个糖果,我们让其中$i$个匹配,剩下$n-i$个瞎匹配的方案数。

这个时候用二项式反演容斥就好了。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MOD = 1000000009;
const int N = 2050;
template<typename T>
inline void read(T&x)
{
    T f = 1,c = 0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){c=c*10+ch-'0';ch=getchar();}
    x = f*c;
}
int fastpow(int x,int y)
{
    int ret = 1;
    while(y)
    {
        if(y&1)ret=1ll*ret*x%MOD;
        x=1ll*x*x%MOD;y>>=1;
    }
    return ret;
}
int n,k,a[N],b[N],dp[N][N],f[N],jc[N],jny[N];
void Mod(int&x){if(x>=MOD)x-=MOD;}
void init()
{
    jc[0] = 1;for(int i=1;i<=n;i++)jc[i]=1ll*jc[i-1]*i%MOD;
    jny[n]=fastpow(jc[n],MOD-2);for(int i=n;i>=1;i--)jny[i-1]=1ll*jny[i]*i%MOD;
}
int C(int x,int y){return 1ll*jc[x]*jny[y]%MOD*jny[x-y]%MOD;}
int main()
{
//    freopen("tt.in","r",stdin);
    read(n),read(k);
    if((n^k)&1){puts("0");return 0;}
    k = (n+k)>>1;init();
    for(int i=1;i<=n;i++)
        read(a[i]);
    for(int i=1;i<=n;i++)
        read(b[i]);
    sort(a+1,a+1+n),sort(b+1,b+1+n);
    dp[0][0]=1;
    for(int i=1,j=0;i<=n;i++)
    {
        while(j+1<=n&&a[i]>b[j+1])j++;
        for(int k=0;k<=j;k++)
        {
            dp[i][k]=dp[i-1][k];
            if(k)Mod(dp[i][k]+=1ll*dp[i-1][k-1]*(j-k+1)%MOD);
        }
    }
    for(int i=1;i<=n;i++)
        f[i] = 1ll*dp[n][i]*jc[n-i]%MOD;
    int ans = 0;
    for(int i=k;i<=n;i++)
    {
        if((i-k)&1)Mod(ans+=MOD-1ll*f[i]*C(i,k)%MOD);
        else Mod(ans+=1ll*f[i]*C(i,k)%MOD);
    }
    printf("%d\n",ans);
    return 0;
}
View Code

 

posted @ 2019-06-20 16:39  LiGuanlin  阅读(149)  评论(0编辑  收藏  举报