二项式反演[bzoj3622]已经没有什么好害怕的了
前言
繼續學習容斥的技巧!
題意簡介
題面鏈接
題目大意
給出兩個數組a,ba,ba,b
求有多少種對應方式使得有恰好kkk對匹配(i,j)(i,j)(i,j)滿足ai>bja_i>b_jai?>bj?
數據范圍
n≤2000,0≤k≤nn\le2000,0\le k\le nn≤2000,0≤k≤n
題解
部分分
這道題的暴力是指數級的,對于這樣的數據范圍顯得非常沒有意義
正解
發現暴力的瓶頸在于枚舉匹配的集合,那么說明我們不能直接枚舉集合
考慮減少限制,使得計算變得簡單,然后再把不滿足的都扔掉,那么就能做出這道題,也就是容斥
我們先將兩個數組分別排序,這樣的話容易發現對于aaa中的一個元素aia_iai?,滿足ai>bja_i>b_jai?>bj?的bjb_jbj?是bbb的一個前綴
定義g[i]g[i]g[i]為最大的下標jjj滿足bj<aib_j<a_ibj?<ai?
定義f[i][j]f[i][j]f[i][j]表示aaa數組的前iii個數,其中確定有jjj對匹配滿足a>ba>ba>b(確定那么多,但不代表只有那么多),在這樣的情況下這jjj對的選擇方案數(這jjj對以外的數的匹配不同算同一種方案)
然后很容易貼出轉移式子f[i][j+1]=f[i?1][j+1]+f[i?1][j]?(g[i]?j)f[i][j+1]=f[i-1][j+1]+f[i-1][j]*(g[i]-j)f[i][j+1]=f[i?1][j+1]+f[i?1][j]?(g[i]?j)
第一項代表當前這個數不匹配,第二項表示當前這個數匹配
設F[i]=f[n][i]?(n?i)!F[i]=f[n][i]*(n-i)!F[i]=f[n][i]?(n?i)!,那么F[i]F[i]F[i]就表示滿足a>ba>ba>b的匹配對數大于等于jjj的對應方式數(注意,這個定義和上面的不同,乘一個階乘后就所有數都固定了)
定義Ans[i]Ans[i]Ans[i]表示滿足a>ba>ba>b的匹配對數等于jjj的對應方式數
容易發現Ans[k]Ans[k]Ans[k]即我們所要的答案
我們發現這樣一個顯然的式子F[k]=∑i=kn(ik)Ans[i]F[k]=\sum_{i=k}^n\binom{i}{k}Ans[i]F[k]=i=k∑n?(ki?)Ans[i]
注:(ij)=C(i,j)\binom{i}{j}=C(i,j)(ji?)=C(i,j)即組合數
二項式反演即可得Ans[k]=∑i=kn(?1)i?k(ik)F[i]Ans[k]=\sum_{i=k}^n(-1)^{i-k}\binom{i}{k}F[i]Ans[k]=i=k∑n?(?1)i?k(ki?)F[i]
那么什么是二項式反演呢?請看下一節
我們先把這題講完,假設你推出了這個,那么就可以直接算答案了
前面dpdpdp的復雜度是Θ(n2)\Theta(n^2)Θ(n2)的,后面的答案計算由于只要算一個所以是Θ(n)\Theta(n)Θ(n)的
綜上,總復雜度Θ(n2)\Theta(n^2)Θ(n2)
二項式反演
二項式反演的經典形式為fn=∑i=0n(?1)i(ni)gi?gn=∑i=0n(?1)i(ni)fif_n=\sum_{i=0}^n(-1)^i\binom{n}{i}g_i\Leftrightarrow g_n=\sum_{i=0}^n(-1)^i\binom{n}{i}f_ifn?=i=0∑n?(?1)i(in?)gi??gn?=i=0∑n?(?1)i(in?)fi?
這篇博客用到的式子是另一個經典模型
fi=∑j=0i(n?jn?i)gj?gi=∑j=0i(?1)i?j(n?jn?i)fjf_i=\sum_{j=0}^i\binom{n-j}{n-i}g_j\Leftrightarrow g_i=\sum_{j=0}^i(-1)^{i-j}\binom{n-j}{n-i}f_jfi?=j=0∑i?(n?in?j?)gj??gi?=j=0∑i?(?1)i?j(n?in?j?)fj?
應用的時候其實是進行了數組翻轉和特殊值代換產生的
要問證明?請看我的另一篇總結博客
代碼
東西講完了,就可以安心寫出前面的那一題了,貼上ac代碼
#include<cstdio> #include<cctype> #include<algorithm> namespace fast_IO {const int IN_LEN=10000000,OUT_LEN=10000000;char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf,*lastin=ibuf+IN_LEN,*lastout=obuf+OUT_LEN-1;inline char getchar_(){return (ih==lastin)&&(lastin=(ih=ibuf)+fread(ibuf,1,IN_LEN,stdin),ih==lastin)?EOF:*ih++;}inline void putchar_(const char x){if(oh==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;*oh++=x;}inline void flush(){fwrite(obuf,1,oh-obuf,stdout);} } using namespace fast_IO; #define getchar() getchar_() #define putchar(x) putchar_((x)) #define rg register typedef long long LL; template <typename T> inline T max(const T a,const T b){return a>b?a:b;} template <typename T> inline T min(const T a,const T b){return a<b?a:b;} template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;} template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;} template <typename T> inline T abs(const T a){return a>0?a:-a;} template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;} template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);} template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;} template <typename T> inline T square(const T x){return x*x;}; template <typename T> inline void read(T&x) {char cu=getchar();x=0;bool fla=0;while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}while(isdigit(cu))x=x*10+cu-'0',cu=getchar();if(fla)x=-x; } template <typename T> inline void printe(const T x) {if(x>=10)printe(x/10);putchar(x%10+'0'); } template <typename T> inline void print(const T x) {if(x<0)putchar('-'),printe(-x);else printe(x); } const LL mod=1000000009; inline void md(LL&x){if(x>=mod)x-=mod;} int n,k; int a[2001],b[2001],g[2001]; inline LL pow(LL x,LL y) {LL res=1;for(;y;y>>=1,x=x*x%mod)if(y&1)res=res*x%mod;return res; } LL fac[2001],inv[2001]; inline LL C(const LL x,const LL y) {return fac[x]*inv[y]%mod*inv[x-y]%mod;} LL f[2001],ans; int main() {read(n),read(k);if((n&1)!=(k&1)){print(0);return flush(),0;}k=(n-k)/2+k;for(rg int i=1;i<=n;i++)read(a[i]);for(rg int i=1;i<=n;i++)read(b[i]);std::sort(a+1,a+n+1);std::sort(b+1,b+n+1);for(rg int i=1,j=0;i<=n;i++){while(j<n&&b[j+1]<a[i])j++;g[i]=j;}fac[0]=1;for(rg int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;inv[n]=pow(fac[n],mod-2);for(rg int i=n;i>=1;i--)inv[i-1]=inv[i]*i%mod;f[0]=1;for(rg int i=1;i<=n;i++)for(rg int j=n-1;j>=0;j--)md(f[j+1]+=f[j]*(g[i]-j)%mod);for(rg int i=k,j=1;i<=n;i++,j^=1){if(j)ans+=f[i]*C(i,k)%mod*fac[n-i]%mod;else ans-=f[i]*C(i,k)%mod*fac[n-i]%mod;}ans%=mod,ans+=mod,ans%=mod;print(ans);return flush(),0; }總結
二項式反演還是挺有效的,也很好用,繼續學習!
總結
以上是生活随笔為你收集整理的二项式反演[bzoj3622]已经没有什么好害怕的了的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 朴素容斥原理[ZJOI2016][bzo
- 下一篇: 对于容斥原理反演的思考和总结