【BZOJ3622】已经没有什么好害怕的了(动态规划+广义容斥)
大致题意: 有\(n\)个糖果和\(n\)个药片,各有自己的能量。将其两两配对,求糖果比药片能量大的组数恰好比药片比糖果能量大的组数多\(k\)组的方案数。
什么是广义容斥(二项式反演)
我们首先来介绍一下什么是广义容斥。
我们要证明下面这样一个式子:
观察右边这个式子,我们将\(f_n=\sum_{i=0}^nC_n^ig_i\)代入就可以得到:
把\(C_n^i\)移入,可以得到:
通过暴力展开我们可以证明\(C_n^iC_i^j=C_n^jC_{n-j}^{i-j}\),代入得:
然后我们调换\(i\)和\(j\)的枚举顺序,并让\(i\)变为原先的\(i-j\)可得:
然后代入二项式定理(因此这又称为二项式反演)\((x+y)^n=\sum_{i=0}^nC_n^ix^iy^{n-i}\)得:
由于式子中出现了一个\(0^{n-j}\),因此只有当\(n-j=0\),即\(n=j\)时,该式才有值,代入得:
因此原式恒成立,得证。
简单转化
恰好多\(k\)组这个条件不是很好求,所以我们可以将其转化一下。
由于总数\(n\)是固定的,所以我们容易求出糖果比药片能量大的组数应为\(\frac{n+k}2\)。
动态规划
接下来,我们就要使用\(DP\)了。
首先,我们把糖果和药片分别按能量排一边序。
则这样可以保证后枚举到的的糖果所能胜过的药片的区间一定能覆盖先枚举到的糖果的区间。
然后,设\(f_{i,j}\)表示糖果匹配到第\(i\)个,且已经与\(j\)个药片匹配的方案数,并设\(g_i\)表示能与第\(i\)个糖果匹配的药片数,则我们可以推出转移方程为:
广义容斥
从前面\(DP\)得到的\(f_{n,i}\),我们可以轻松推得钦定\(i\)对糖果大于药片方案数。
由于我们已经确定了\(i\)对,而剩下的\((n-i)\)对实际上可以随意匹配,因此便是一个全排列。即:
我们可以设\(F_i\)表示糖果大于药片的对数恰好等于\(i\)的方案数,则可得:
然后用先前提到的广义容斥,就可以得到:
已知糖果比药片能量大的组数应为\(\frac{n+k}2\),则\(F_{\frac{n+k}2}\)即为答案。
而这可以直接求。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 2000
#define X 1000000009
#define Qinv(x) Qpow(x,X-2)
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
#define C(x,y) (1LL*Fac[x]*Inv[y]%X*Inv[(x)-(y)]%X)
using namespace std;
int n,k,a[N+5],b[N+5],g[N+5],Fac[N+5],Inv[N+5],f[N+5][N+5];
I int Qpow(RI x,RI y) {RI res=1;W(y) y&1&&(res=1LL*res*x%X),x=1LL*x*x%X,y>>=1;return res;}//快速幂
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C^FS?FO[C++]=c:(fwrite(FO,1,C,stdout),FO[(C=0)++]=c))
#define tn(x) (x<<3)+(x<<1)
#define D isdigit(c=tc())
int T,C;char c,*A,*B,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn(x)+(c&15),D);}
Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
I void clear() {fwrite(FO,1,C,stdout),C=0;}
}F;
int main()
{
RI i,j,t,ans=0;for(F.read(n,k),i=1;i<=n;++i) F.read(a[i]);for(i=1;i<=n;++i) F.read(b[i]);//读入数据
for(sort(a+1,a+n+1),sort(b+1,b+n+1),i=1;i<=n;++i) {g[i]=g[i-1];W(g[i]^n&&a[i]>b[g[i]+1]) ++g[i];}//排序,然后求出g[i]
for(f[0][0]=i=1;i<=n;++i) for(j=0;j<=i;++j) f[i][j]=f[i-1][j],j&&Inc(f[i][j],1LL*f[i-1][j-1]*(g[i]-j+1)%X);//DP转移
for(Fac[0]=i=1;i<=n;++i) Fac[i]=1LL*Fac[i-1]*i%X;for(Inv[n]=Qinv(Fac[n]),i=n-1;~i;--i) Inv[i]=1LL*Inv[i+1]*(i+1)%X;//初始化阶乘及阶乘逆元
for(i=t=n+k>>1;i<=n;++i) Inc(ans,1LL*((i^t)&1?X-1:1)*C(i,t)%X*f[n][i]%X*Fac[n-i]%X);//求出最终答案
return F.write(ans),F.clear(),0;//输出答案
}