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

题意:

n n n个范围 [ l i , r i ] , a i ∈ [ l i , r i ] [l_i,r_i],a_i\in[l_i,r_i] [li,ri],ai[li,ri],求所有符合限制的单调不增序列的元素和的总和。

解题思路:

因为是a是单调不降的,所以他们区间的左端点应该是单调不降的,右端点是单调不增的。可以分区间讨论。
2n个点划分出2n个区间,然后用 d p ( i , j ) dp(i,j) dp(i,j)表示考虑了前i个区间,选择了j个数字的符合条件的序列的元素和。用 f ( i , j ) f(i,j) f(i,j)表示前i个区间选了j个数字的方案数,那么枚举有多少个数在当前区间范围内即可转移。
怎么转移呢?
区间有x个数字,可重复选出y个数字组成单调不增序列的方案数,可以转换为有x个桶,放y个小球的方案数。那么用隔板法我们知道方案数为 C ( x + y − 1 , x − 1 ) C(x+y-1, x-1) C(x+y1,x1).
这样前面的总和要乘上这么多的方案数。然后再看当前新加的数字的贡献:因为是所有方案的总和,所以可以取平均,在 [ l , r ] [l,r] [l,r]选一个数字相当于贡献 ( r + l ) / 2 (r+l)/2 (r+l)/2。那么选 y y y个对 d p ( i , j ) dp(i,j) dp(i,j)的额外贡献为:
单 个 数 字 贡 献 ∗ 数 字 个 数 ∗ 前 面 区 间 选 取 的 方 案 数 ∗ 当 前 区 间 可 以 选 取 的 方 案 数 单个数字贡献*数字个数* 前面区间选取的方案数 * 当前区间可以选取的方案数
其中单个数字贡献为 ( r + l ) / 2 (r+l)/2 (r+l)/2
数字个数为 y y y
前面区间选取方案为 f ( i − 1 , j − y ) f(i-1,j-y) f(i1,jy)
当前区间选取方案数用隔板法算一下
这样就得到了 d p ( i , j ) dp(i,j) dp(i,j)的转移。
f ( i , j ) f(i,j) f(i,j)的转移就是 当 前 区 间 方 案 数 ∗ 之 前 区 间 方 案 数 当前区间方案数*之前区间方案数 的累加了。
具体看代码吧:
细节:

  1. 隔板法算出来的组合数不要预处理,直接算就行了,因为数字个数很少。
  2. 在计算 d p dp dp的转移的时候与区间端点做乘之前先模,不然爆long long(63行)
#include
#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:"<
    }
    for(int i = 1; i <= n; ++i){
        scanf("%lld", &r[i]);
        //cc[++num] = r[i]+1;
        //cout<<"R:"<
    }
    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:"<
        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:"<
                        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:"<
                }else break;
            }
           // cout<<"i:"<
        }
    }
    //cout<
    ll ans = dp[num-1][n];
    //assert(ans > 0);
    cout<<ans<<endl;
}
/*
2
1 2
3 4
*/

你可能感兴趣的:(dp,训练补题)