AtCoder Beginner Contest 234 G - Divide a Sequence

AtCoder Beginner Contest 234 G - Divide a Sequence

题目

给定一个长度为N的序列,任意的将其划分为任意长度大于零的若干个子串,对于任意一个子串 S i S_i Si有一个权值 m a x ( S i ) − m i n ( S i ) max(S_i) - min(S_i) max(Si)min(Si),将其设为 V i V_i Vi,对于整个序列,其权值 W = V 1 ∗ V 2 ∗ V 3 … ∗ V k W = V_1 * V_2 * V_3 \ldots * V_k W=V1V2V3Vk,现求对于长度为N的序列,所有可能划分产生的 W W W的和 S u m Sum Sum

思路

我们用DP的思想思考这个问题,如果我们知道了长 1 1 1 i − 1 i - 1 i1的序列的 S u m Sum Sum,那么对于长度为 i i i的序列,其和前状态本质在于所有加入 a i a_i ai,的子串,对于这样一个子串,我们设其为{ a j , a j + 1 , a j + 2 , … , a i a_j,a_{j+1},a_{j+2},\ldots,a_i aj,aj+1,aj+2,,ai},我们可以算出它的 v v v,可得
d p [ i ] = d p [ i ] + d p [ i − j − 1 ] ∗ v dp[i] = dp[i] + dp[i - j - 1] * v dp[i]=dp[i]+dp[ij1]v
枚举j,可以得到朴素的转移方程
d p i = ∑ j = 0 i d p j ∗ max ⁡ ( a j + 1 , a j + 2 , … , a i ) − d p j ∗ min ⁡ ( a j + 1 , a j + 2 , … , a i ) dp_i = \sum_{j = 0}^i {dp_j*\max(a_{j + 1},a_{j + 2},\ldots,a_i) - dp_j*\min(a_{j + 1},a_{j + 2},\ldots,a_i) } dpi=j=0idpjmax(aj+1,aj+2,,ai)dpjmin(aj+1,aj+2,,ai)
如果直接暴力枚举的时间复杂度是 O ( n 2 ) O(n^2) O(n2)的,无法通过本题,考虑优化

首先, m a x max max m i n min min可以分开计算
d p i = ∑ j = 0 i d p j ∗ max ⁡ ( a j + 1 , a j + 2 , … , a i ) − ∑ j = 0 i d p j ∗ min ⁡ ( a j + 1 , a j + 2 , … , a i ) dp_i = \sum_{j = 0}^i {dp_j *\max(a_{j + 1},a_{j + 2},\ldots,a_i) } -\sum_{j = 0}^i {dp_j *\min(a_{j + 1},a_{j + 2},\ldots,a_i)} dpi=j=0idpjmax(aj+1,aj+2,,ai)j=0idpjmin(aj+1,aj+2,,ai)
对于一段后缀最值相关可以尝试用单调栈优化
观察以下dp转移方程

    dp[1] = dp[0] * (max(a[1]) - min(a[1]))
    
    dp[2] = dp[0] * (max(a[1],a[2]) - min(a[1],a[2])) 
          + dp[1] * (max(a[2]) - max(a[1]))
 
    dp[3] = dp[0] * (max(a[1],a[2],a[3]) - min(a[1],a[2],a[3]))
          + dp[1] * (max(a[2],a[3]) - min(a[2],a[3]))
          + dp[2] * (max(a[3]) - min(a[3]))

可以发现,答案都是一段后缀最值乘以某个dp值,最后对这些值求前缀和的过程,但每一次加入新的元素,这个前缀和的某些项就会发生改变。
这个过程可以说是用单调栈维护区间最值的同时,动态维护一段前缀和 m x s u m mxsum mxsum的过程。(对于最小值同理)
m x s u m = ∑ j = 0 i d p j ∗ max ⁡ ( a j + 1 , a j + 2 , … , a i ) mxsum = \sum_{j = 0}^i {dp_j *\max(a_{j + 1},a_{j + 2},\ldots,a_i) } mxsum=j=0idpjmax(aj+1,aj+2,,ai)
考虑如何用单调栈维护 m x s u m mxsum mxsum,我们设 m x s u m mxsum mxsum的任意项为 s i s_i si
对于单调栈,它维护的是第i个元素作为后缀最值的最长后缀 [ l , i ] [l,i] [l,i],那么 [ l , i ] [l,i] [l,i]的所有后缀子段都会把最值修改为 a i a_i ai,也即我们要更新的 s i s_i si。(或者说是由 a i a_i ai,所统领的所有 s s s)

因此每次弹出栈内元素的过程中,如果我们知道弹出元素所统领的 s i s_i si中的 d p [ ] dp[] dp[]值都是哪些,就可以完成对 m x s u m mxsum mxsum的一次更新。

统计这些 d p [ ] dp[] dp[]值,同样可以通过定义一个这样的 d p dp dp数组: d p m x i dpmx_i dpmxi表示由第i个元素所统领的 s s s所属的 d p [ ] dp[] dp[]的和。
可写出转移方程:
d p m x i = d p m x i + d p m x j dpmx_i = dpmx_i + dpmx_j dpmxi=dpmxi+dpmxj
而j就是单调栈弹出的元素的下标。
这样我们就可以在单调栈维护过程中顺势维护了 m x s u m mxsum mxsum。对于 m n s u m mnsum mnsum同理。

最后,成功的把时间复杂度优化到了 O ( n ) O(n) O(n)

AC代码

#include <bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define linf 0x3f3f3f3f3f3f3f3f
#define ll long long
#define ull unsigned long long
#define debug(x) cout<<"> "<< x<<endl;
#define endl '\n'
#define lowbit(x) x&-x
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int MAXN =10 + 2e5, mod = 998244353;

void solve()
{    
    int N;
    cin >> N;
    vector<int> a(N + 1);
    for(int i = 1;i <= N;i ++) cin >> a[i];
    // dp[i] = sum(dp[j] * max(a[j + 1 ~ i])) - sum(dp[j] * min(a[j + 1 ~ i]))
    vector<ll> dp(N + 1,0),dpmx(N + 1,0),dpmn(N + 1,0);
    dp[0] = dpmx[0] = dpmn[0] = 1;

    stack<int> Mx,Mn;
    int mxsum = 0,mnsum = 0;
    for(int i = 1;i <= N;i ++){
        dpmx[i] = dp[i - 1];// 长为1的后缀一定由ai统领
        while(Mx.size() && a[Mx.top()] < a[i]) {
            int id = Mx.top();
            Mx.pop();
            dpmx[i] = (dpmx[i] + dpmx[id]) % mod;// 求须修改项的dp[]和
            mxsum = (mxsum - dpmx[id] * a[id] % mod + mod) % mod;// 先减掉须修改值
        }
        Mx.push(i);
        mxsum = (mxsum + dpmx[i] * a[i] % mod + mod) % mod;// 加上修改后的值
        // 完成一次对mxsum的维护 
        dpmn[i] = dp[i - 1];
        while(Mn.size() && a[Mn.top()] > a[i]) {
            int id = Mn.top();
            Mn.pop();
            dpmn[i] = (dpmn[i] + dpmn[id]) % mod;
            mnsum = (mnsum - dpmn[id] * a[id] % mod + mod) % mod;
        }
        Mn.push(i);
        mnsum = (mnsum + dpmn[i] * a[i] % mod + mod) % mod;

        dp[i] = (mxsum - mnsum + mod) % mod;
    }
    cout << dp[N] << endl;
}
signed main()
{
    ios::sync_with_stdio();cin.tie();cout.tie();

    solve();

    return 0;
}

后记

算是我的单调栈和单调队列入门题了···,从知道这个概念到AC此题对我而言真的是个很困难的过程。中间查了资料,写了模板题,才回来理清这题的思路。十分曲折了可以说。所以尽力写好这篇博客。可最后还是有些不尽人意,感觉还有很多地方说的啰嗦或者不够切中要害,还是对单调栈和DP的理解不够深入。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值