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; }