CF403D Beautiful Pairs of Numbers
題意
在一個長度為 \(n\) 的序列中,找 \(k\) 個長度互不相同的區間互不重疊,求方案數。
Solution
經過簡單的計算,發現普通的組合數並不能直接把方案數算出來,因為要考慮的情況實在太多了。那麼我們可以考慮另一種能夠算出方案數的方法——計數DP。
在上面的題意中,我將本題目的重點加粗了。那麼我們可以設 \(f_{i,j}\) 表示 \(i\) 個不同的區間,總長度為 \(j\) 且區間長度遞增的方案數(為什麼要遞增下面會說)。則狀態轉移方程為 \(f_{0,0}=1,f_{i,j}=f_{i,,j-i}+f_{i-1,j-i}\) ,指的是可以將原方案的 \(i\) 個區間都加上一,或者加上一再多一個長度為 \(1\)
那麼這個東西和答案有什麼關係呢?
這裡我們先把最後答案的式子寫出來, \(ans=k!\sum\limits_{i=1}^n\binom {n-i+k}kf_{k,i}\)
意思是列舉區間總長 \(i\) ,會出現 \(n-i\) 的空隙,那麼就是在 \(n-i\) 的空隙和 \(k\) 個區間中選 \(k\) 個區間(或者像上面那份題解一樣,將區間看成點),那麼那個 \(i\) 就是要用到 \(f\) 的地方,但是因為是遞增,所以要乘上 \(A_k^k=k!\) 代表全排列,此時答案為 \(ans=\sum\limits_{i=1}^n\binom {n-i+k}kk!f_{k,i}\)
因為階乘,組合數和 \(f\) 在運算過程中和 \(n,k\) 無關,所以可以提前預處理,直接查詢。
小細節:因為如果選擇區間長度為 \(1,2,3\cdots\) ,可得 \(\dfrac {k(k+1)}2\leq1000\) ,解得 \(k\leq 45\) ,所以可以特判一下(我特判的50)
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ll long long using namespace std; const int N=1010,mod=1e9+7; int t,n,k; ll fac[N],inv[N],f[60][N],ans[N][60]; inline int read(){ int x=0,f=1; char ch=getchar(); while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();} return x*f; } inline ll mul(ll a,ll b){return a*(ll)b%mod;} inline ll add(ll a,ll b){return a+b>mod?a+b-mod:a+b;} inline ll fpow(ll a,ll b){ ll res=1; while(b){ if(b&1) res=mul(res,a); a=mul(a,a); b>>=1; } return res; } inline void init(){ fac[0]=1; for(int i=1;i<=1000;i++) fac[i]=mul(fac[i-1],i); inv[1000]=fpow(fac[1000],mod-2); for(int i=1000;i>0;i--) inv[i-1]=mul(inv[i],i); } inline ll C(ll n,ll m){ if(n<m) return 0; return mul(fac[n],mul(inv[n-m],inv[m])); } int main(){ init(); f[0][0]=1; for(int i=1;i<=50;i++) for(int j=i*(i+1)/2;j<=1000;j++) f[i][j]=add(f[i][j-i],f[i-1][j-i]); for(int i=1;i<=1000;i++) for(int k=1;k<=50;k++){ for(int j=k*(k+1)/2;j<=i;j++){ ans[i][k]=add(ans[i][k],mul(C(i-j+k,k),f[k][j])); } ans[i][k]=mul(ans[i][k],fac[k]); } t=read(); while(t--){ n=read();k=read(); if(k>50) puts("0"); else printf("%lld\n",ans[n][k]); } return 0; }