2020 CCPC Wannafly Winter Camp Day6 D. 遞增遞增(DP)

題意:

nn個範圍[li,ri],ai[li,ri][l_i,r_i],a_i\in[l_i,r_i],求所有符合限制的單調不增序列的元素和的總和。

解題思路:

因爲是a是單調不降的,所以他們區間的左端點應該是單調不降的,右端點是單調不增的。可以分區間討論。
2n個點劃分出2n個區間,然後用dp(i,j)dp(i,j)表示考慮了前i個區間,選擇了j個數字的符合條件的序列的元素和。用f(i,j)f(i,j)表示前i個區間選了j個數字的方案數,那麼枚舉有多少個數在當前區間範圍內即可轉移。
怎麼轉移呢?
區間有x個數字,可重複選出y個數字組成單調不增序列的方案數,可以轉換爲有x個桶,放y個小球的方案數。那麼用隔板法我們知道方案數爲C(x+y1,x1)C(x+y-1, x-1).
這樣前面的總和要乘上這麼多的方案數。然後再看當前新加的數字的貢獻:因爲是所有方案的總和,所以可以取平均,在[l,r][l,r]選一個數字相當於貢獻(r+l)/2(r+l)/2。那麼選yy個對dp(i,j)dp(i,j)的額外貢獻爲:
單個數字貢獻*數字個數* 前面區間選取的方案數 * 當前區間可以選取的方案數
其中單個數字貢獻爲(r+l)/2(r+l)/2
數字個數爲yy
前面區間選取方案爲f(i1,jy)f(i-1,j-y)
當前區間選取方案數用隔板法算一下
這樣就得到了dp(i,j)dp(i,j)的轉移。
f(i,j)f(i,j)的轉移就是 當前區間方案數*之前區間方案數 的累加了。
具體看代碼吧:
細節:

  1. 隔板法算出來的組合數不要預處理,直接算就行了,因爲數字個數很少。
  2. 在計算dpdp的轉移的時候與區間端點做乘之前先模,不然爆long long(63行)
#include<bits/stdc++.h>
#define ll long long
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
using namespace std;
/*注意爆long long*/
const int maxn = 205;
ll cc[200];
int num = 0;
int n;
ll l[55], r[55];
const ll mod = 998244353;
ll dp[200][200];
ll f[200][200];
ll fac[maxn], ifac[maxn], inv[maxn];
ll qm(ll a, ll b){ll res = 1; while(b) {if(b&1) res=res*a%mod; a = a*a%mod; b>>=1;} return res;}
ll Choose(ll a, ll b){//a個可選的數字,b個要放的數 C(b+a-1, b)
    ll res = 1;
    for(ll i = a; i < b+a; ++i) res = i%mod*res%mod;
    for(ll i = 2; i <= b; ++i)  res = res*inv[i]%mod;
    return res;
}
int main()
{
    fac[0] = ifac[0] = 1;
    for(int i = 1; i < maxn; ++i) fac[i] = fac[i-1]*i%mod, ifac[i] = qm(fac[i], mod-2), inv[i] = qm(i, mod-2);
    cin>>n;
    for(int i = 1; i <= n; ++i){
        scanf("%lld", &l[i]); l[i] = max(l[i], l[i-1]);
        cc[++num] = l[i];
        //cout<<"L:"<<l[i]<<endl;
    }
    for(int i = 1; i <= n; ++i){
        scanf("%lld", &r[i]);
        //cc[++num] = r[i]+1;
        //cout<<"R:"<<r[i]+1<<endl;
    }
    cc[++num] = r[n]+1;
    for(int i = n-1; i >= 1; --i){
        r[i] = min(r[i], r[i+1]);
        cc[++num] = r[i]+1;
    }
    sort(cc+1,cc+1+num);
    num = unique(cc+1,cc+1+num)-cc-1;
    assert(num < 200);
    ll inv2 = (mod+1)/2;
    f[0][0] = 1;
    for(int i = 1; i < num; ++i){
        dp[i][0] = 0;
        f[i][0] = 1;
        ll L = cc[i], R = cc[i+1]-1;
        //cout<<"L:"<<L<<" R:"<<R<<endl;
        for(int j = 1; j <= n; ++j){
            dp[i][j] = dp[i-1][j];
            f[i][j] = f[i-1][j];
            for(int k = j; k > 0; --k){
                if(l[k] <= L && R <= r[k]){
                        //cout<<"i:"<<i<<" j:"<<j<<" k:"<<k<<" L:"<<L<<" R:"<<R<<endl;
                        dp[i][j] = (dp[i][j] +
                                    dp[i-1][k-1]*Choose(R-L+1, j-k+1)%mod +
                                    f[i-1][k-1]*Choose(R-L+1, j-k+1)%mod*(R%mod+L%mod)%mod*inv2%mod*(j-k+1)%mod )%mod;
                        f[i][j] = (f[i][j] + f[i-1][k-1]*Choose(R-L+1, j-k+1)%mod)%mod;
                        //cout<<"dp:"<<dp[i][j]<<endl;
                }else break;
            }
           // cout<<"i:"<<i<<"j:"<<j<<" dp:"<<dp[i][j]<<endl;
        }
    }
    //cout<<dp[1][1]<<endl;
    ll ans = dp[num-1][n];
    //assert(ans > 0);
    cout<<ans<<endl;
}
/*
2
1 2
3 4
*/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章