1. 程式人生 > >計數難題5:[APIO2016]划艇

計數難題5:[APIO2016]划艇

計數難題5:[APIO2016]划艇

標籤(空格分隔): 計數難題題選

題目大意:

給定\(n\)個區間\([l_i,r_i]\)
你可以從第\(i\)個區間中選出一個整數元素\(a_i\in [l_i,r_i]\),也可以不選。
要求選出的元素按標號順序排列後構成一個嚴格單調遞增序列。
求至少選出一個元素的合法方案數。
答案對\(10^9+7\)取模。
資料範圍:\(n\leq 500\) , \(l_i\leq r_i \leq 10^9\)

題解

可以想到把區間離散化。
\(f_{i,j}\) 表示考慮完前\(i\)個區間,當前區間必須選一個元素,且這個元素在第\(j\)段的方案數。
轉移的時候,暴力列舉上一個不在同一區間的元素選在哪個區間\(k\)


\([k+1,i]\)範圍內能夠在第\(j\)段選數的區間個數為\(m\),設當前這段的長度為\(len\)
我們再暴力列舉這\(m\)個元素中有\(l\)個元素在當前這段中選了。
那麼就可以得到轉移方程:
\[f_{i,j} = \sum_{k=0}^{i-1} (\sum_{t=1}^{j-1} f_{k,t})(\sum_{l=1}^{min(m,len)} \binom{m-1}{l-1}\binom{len}{l})\]
顯然\(\sum_{t=1}^{j-1} f_{k,t}\) 是可以記字首和的。
所以我們現在的問題變為求\(\sum_{l=1}^{min(m,len)} \binom{m-1}{l-1}\binom{len}{l}\)

我們有\(\sum_{l=1}^{min(m,len)} \binom{m-1}{l-1}\binom{len}{l} = \sum_{l=1}^{min(m,len)} \binom{m-1}{m-l}\binom{len}{l}\)
所以可以用範德蒙恆等式化簡:

\[\sum_{l=1}^{min(m,len)} \binom{m-1}{m-l}\binom{len}{l} = \binom{m+len-1}{m}\]
現在的轉移方程就變為了:
\[f_{i,j} = \sum_{k=0}^{i-1} \binom{m+len-1}{m} (\sum_{t=1}^{j-1} f_{k,t})\]

唯一的問題就是如何求\(\binom{m+len-1}{m}\)了,因為\(len\)可能達到\(10^9\)級別。
注意到組合數的改變只與\(m\)有關。
所以組合數的變化是上下同時\(+1\)
所以可以套用吸收-歸納恆等式:\(\binom{n+1}{m+1} = \frac{n+1}{m+1} \binom{n}{m}\)
我們列舉\(k\),然後在轉移前先預處理好組合數即可。

實現程式碼

#include<bits/stdc++.h>
#define IL inline
#define _ 1015
#define ll long long
using namespace std ;

IL int gi(){
    int data = 0 , m = 1; char ch = 0;
    while(ch!='-' && (ch<'0'||ch>'9')) ch = getchar();
    if(ch == '-'){m = 0 ; ch = getchar() ; }
    while(ch >= '0' && ch <= '9'){data = (data<<1) + (data<<3) + (ch^48) ; ch = getchar(); }
    return (m) ? data : -data ; 
}

#define mod 1000000007

int f[_][_] , g[_][_] ;
int Ans ; 
int n,m,L[_],R[_],X[_],A[_],B[_],inv[_],len[_],Comb[_] ;

int main() {
    n = gi() ;
    for(int i = 1; i <= n; i ++) L[i] = gi() , R[i] = gi() ; 
    for(int i = 1; i <= n; i ++) X[++m] = L[i] , X[++m] = R[i] + 1 ; 
    X[++m] = 0 ; 
    sort(X + 1 , X + m + 1) ; 
    X[m+1] = X[m] + 1 ; ++ m ;  
    m = unique(X + 1 , X + m + 1) - X - 1 ;
    for(int i = 1; i <= m; i ++) len[i] = (X[i + 1] - X[i]) % mod ;
    for(int i = 1; i <= n; i ++) {
        A[i] = lower_bound(X + 1 , X + m + 1 , L[i]) - X ;
        B[i] = upper_bound(X + 1 , X + m + 1 , R[i]) - X - 1 ; 
    }
    -- m ;
    inv[0] = inv[1] = 1 ;
    for(int i = 2; i <= n + 1; i ++) inv[i] = 1ll * (mod-mod/i) * inv[mod%i] % mod ;
    f[0][0] = 1 ;
    g[0][0] = f[0][0] ;
    for(int j = 1; j <= m; j ++) g[0][j] = (f[0][j] + g[0][j - 1]) % mod ;
    for(int i = 1,a,b; i <= n; i ++) {
        for(int j = A[i]; j <= B[i]; j ++) {
            a = (len[j] - 1) % mod ; b = 0 ;
            Comb[i] = 1 ;
            for(int k = i - 1; k >= 0; k --) {
                if(A[k + 1] <= j && j <= B[k + 1]) {
                    ++ a ; ++ b ;
                    Comb[k] = 1ll * a * Comb[k + 1] % mod * inv[b] % mod ;
                }
                else Comb[k] = Comb[k + 1] ;
            }
            for(int k = i - 1; k >= 0; k --) f[i][j] = (f[i][j] + 1ll * Comb[k] * g[k][j-1] % mod) % mod ; 
        }
        g[i][0] = f[i][0] ;
        for(int j = 1; j <= m; j ++) g[i][j] = (f[i][j] + g[i][j - 1]) % mod ;
        Ans = (Ans + g[i][m]) % mod ; 
    }
    cout << Ans % mod << endl ;
    return 0 ; 
}