bzoj3622 已经没有什么好害怕的了
题目描述:
题解:
$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; }