bzoj 3622

首先需要知道二项式反演的一个推论:$f(k)=\sum_{i=k}^{n}C_{i}^{k}g(i)$,则$g(k)=\sum_{i=k}^{n}(-1)^{i-k}C_{i}^{k}f(i)$

然后我们考虑如果糖果多于药片的比药片多与糖果的多$k$个,那么糖果多于药片的个数应该为$\frac{n+k}{2}$个

然后就...dp吧

设状态$dp[i][j]$表示已经配对了前$i$个,至少有$j$组满足“糖果”>“药片”的取法数(只考虑满足要求的部分的取法,剩余部分最后乘一个排列(随意配对)即可)

考虑将两个序列分别排序,用一个数组$o[i]$来统计对于每一个“糖果”,有多少“药片”比他小

那么转移即为$dp[i][j]=dp[i-1][j]+dp[i-1][j-1]*(o[i]-j+1)$

即对于每一个糖果,都有两种转移方向:一种这种糖果不用,匹配数直接继承,另一种是这种糖果找一个比他小的去匹配,乘对应的方案即可

最后做一下二项式反演即可,即我们要求的是恰好为$\frac{n+k}{2}$的个数,那么$g_{\frac{n+k}{2}}=\sum_{i=\frac{n+k}{2}}^{n}(-1)^{i-\frac{n+k}{2}}C_{i}^{\frac{n+k}{2}}dp[n][i](n-i)!$

贴代码:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define ll long long
using namespace std;
const ll mode=1000000009;
int n,k;
int a[2005],b[2005];
int o[2005];
ll C[2005][2005];
ll dp[2005][2005];
ll mul[2005];
void init()
{
    C[0][0]=mul[0]=1;
    for(int i=1;i<=2000;i++)
    {
        mul[i]=mul[i-1]*i%mode,C[i][0]=1;
        for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j]+C[i-1][j-1])%mode;
    }
}
int main()
{
    scanf("%d%d",&n,&k);
    init();
    if((n+k)&1){printf("0\n");return 0;}
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)scanf("%d",&b[i]);
    sort(a+1,a+n+1),sort(b+1,b+n+1);
    int pos=0;
    for(int i=1;i<=n;i++)
    {
        o[i]=o[i-1];
        while(b[pos+1]<a[i]&&pos<n)o[i]++,pos++;
    }
    for(int i=0;i<=n;i++)dp[i][0]=1;
    for(int i=1;i<=n;i++)for(int j=1;j<=i;j++)dp[i][j]=(dp[i-1][j]+dp[i-1][j-1]*(o[i]-j+1)%mode)%mode;
    ll ans=0,x0=1;
    for(int i=(n+k)>>1;i<=n;i++)ans=(ans+x0*C[i][(n+k)>>1]%mode*mul[n-i]%mode*dp[n][i]%mode)%mode,x0=mode-x0;
    printf("%lld\n",ans);
    return 0;
}

 

posted @ 2019-06-19 20:28  lleozhang  Views(134)  Comments(0Edit  收藏  举报
levels of contents