1. 程式人生 > >[BZOJ3622]已經沒有什麼好害怕的了(容斥DP)

[BZOJ3622]已經沒有什麼好害怕的了(容斥DP)

給定兩個陣列a[n]與b[n](數全不相等),兩兩配對,求“a比b大”的數對比“b比a大”的數對個數多k的配對方案數。

 

據說做了這題就沒什麼題好害怕的了,但感覺實際上這是一個套路題,只是很難想到。

首先顯然“a比b大”的個數是確定的,問題轉化成求“a比b大”的數對個數為m的方案數。

不好算考慮容斥,總結下容斥的一些套路。(From ATP's Blog)

1.全部-至少一個+至少兩個-…=一個也沒有的

2.所有的-一個也沒有的=至少有一個的

3.至少有k個的-C(k+1,k)* 至少有k+1個的+C(k+2,k) *至少有k+2個的…=恰好有k個的

4.將“強制一部分滿足要求,一部分不滿足”轉化為只強制一部分滿足要求,然後容斥

5.補集轉化 \ Min-Max容斥 \ 與質因子相關的容斥用$\mu$做容斥係數。

(還有一種類似FMT思想的容斥,不過只是用於求解問題中簡便使用的,一般不作為容斥方法)

這題用的是第三種,同樣的題可見[BZOJ3198][SDOI2013]Spring(容斥+Hash)

這樣,問題就變成求“a比b大”的數對個數不少於m的方案數。

 

將a,b均排序,考慮DP,f[i][j]表示給前i個a中的至少j個安排數值更低的b的方案數。

則有:f[i][j]=f[i-1][j]+f[i-1][j-1]*(k-(j-1))

其中k為b中小於a[i]的最後一個數的位置。

理解起來就是,如果i與k之後的配對,方案數為f[i-1][j],否則i有k-(j-1)種選法。

問題得解,注意特判n和m不同奇偶,DP轉移是特判j=0即可。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 4 typedef long long ll;
 5 using namespace std;
 6 
 7 const int N=2010;
 8 const ll mod=1000000009;
 9 int n,m;
10 ll ans;
11 ll f[N][N],C[N][N],fac[N];
12 int a[N],b[N]; 13 14 int main(){ 15 freopen("bzoj3622.in","r",stdin); 16 freopen("bzoj3622.out","w",stdout); 17 scanf("%d%d",&n,&m); 18 if ((n^m)&1){ puts("0"); return 0; } 19 m=(n+m)>>1; fac[0]=1; 20 rep(i,1,n) fac[i]=fac[i-1]*i%mod; 21 rep(i,1,n) scanf("%d",&a[i]); 22 rep(i,1,n) scanf("%d",&b[i]); 23 rep(i,0,n){ 24 C[i][0]=1; 25 rep(j,1,i) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod; 26 } 27 sort(a+1,a+n+1); sort(b+1,b+n+1); 28 f[0][0]=1; 29 rep(i,1,n){ 30 int k=1; while (k<=n && b[k]<a[i]) k++; k--; 31 rep(j,1,i) f[i][j]=(f[i-1][j]+f[i-1][j-1]*max(k-j+1,0))%mod; 32 f[i][0]=f[i-1][0]; 33 } 34 ll t=1; 35 rep(i,m,n) f[n][i]=(f[n][i]*fac[n-i])%mod,ans=(ans+t*f[n][i]*C[i][m]%mod+mod)%mod,t=-t; 36 printf("%lld\n",(ans+mod)%mod); 37 return 0; 38 }