1. 程式人生 > 實用技巧 >CF403D Beautiful Pairs of Numbers

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}\)

,將 \(k!\) 提前,就是上面的式子了。

因為階乘,組合數和 \(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;
}