Codeforces-1667: B Optimal Partition

Codeforces-1667 B: Optimal Partition

题目传送门:Codeforces-1667 B

题目

题目截图

在这里插入图片描述

样例描述

在这里插入图片描述

题目大意

  给定一个长度为 n n n 的数组 a a a,希望将 a a a 切成几个连续的段。对于给定 l ⋯ r l \cdots r lr 的一段连续子数组 a l , a l + 1 , ⋯   , a r a_l, a_{l+1},\cdots,a_r al,al+1,,ar,令 s = a l + a l + 1 + ⋯ + a r s=a_l+a_{l+1}+\cdots+a_r s=al+al+1++ar,则该段的价值为:
v l ⋯ r = { ( r − l + 1 )    i f    s > 0 , 0    i f    s = 0 − ( r − l + 1 )    i f    s < 0 v_{l\cdots r} = \left\{ \begin{array}{ll} (r-l+1) \; if \; s > 0, \\ 0 \; if \; s = 0 \\ -(r-l+1) \; if \; s < 0 \end{array} \right. vlr=(rl+1)ifs>0,0ifs=0(rl+1)ifs<0
  问将该数组切成几个非空连续的段后,所能获得的最大价值总和是多少。

题目解析

  这道题很有趣,首先根据题目,不难判断出这是一个 d p dp dp 问题。设 d p i dp_i dpi 代表从 1 ⋯ i 1\cdots i 1i 能够得到的最大价值总和,那么 d p i = max ⁡ j { d p j + v j + 1 ⋯ i } dp_i = \max_j \{dp_j + v_{j+1 \cdots i}\} dpi=maxj{dpj+vj+1i},尽管 v v v 值我们可以靠预处理前缀和 O ( 1 ) O(1) O(1) 得到,但是找 i i i 的过程仍然是 O ( n 2 ) O(n^2) O(n2) 的,这样是过不了这道题的。
  考虑如何更快地进行解题,首先从这个条件入手,看题解发现,当一段长度大于 1 1 1 的区间 s > 0 s > 0 s>0 时,它加入答案才能获得更大的收益,否则,我们都可以通过拆的方式将它拆成长度为 1 1 1 的几个小段。因为 s < 0 s < 0 s<0 时,我们将整个区间再进行拆分,那么得到的结果如果有 s > 0 s > 0 s>0 的部分,则结果更优,否则结果也不会更差。如果 s = 0 s = 0 s=0 的话,我们也可以对整个区间进行拆分,若长度是偶数,那么可以分成左右两部分,结果不会更差( s s s 要么都是 0 0 0,要么一正一负;若长度为奇数,则暂时将该段分成左、中(长度为 1 1 1)、右三部分,若左或右的 s < 0 s < 0 s<0,那么将其分成两段(左,中右)或(左中,右)两部分的话,结果会更好。其余情况可以直接拆成(左、中、右)三部分,结果不会更差。因此最后长度大于 1 1 1 的子段,我们只需要计算 s > 0 s > 0 s>0 的,其余情况只计算长度为 1 1 1 的即可。
  我们考虑如何选取子段 s > 0 s > 0 s>0 的区间,首先设 p i p_i pi 为代表 a 1 + a 2 + ⋯ a i a_1+a_2+\cdots a_i a1+a2+ai 的前缀和。若我们可以对前缀和进行排序,当我们已知当前位置 i i i 的前缀和排位 o r d i ord_i ordi 时,我们可以通过查找前缀和比其小的段 1 ⋯ j 1\cdots j 1j o r d j < o r d i ord_j < ord_i ordj<ordi),且 j < i j<i j<i 进行更新。这样我们可以保证 j + 1 ⋯ i j+1 \cdots i j+1i 区间的 v v v 值是正的,我们可以直接使用 i − j i-j ij 表示这一段的价值。
  但尽管这样能够保证正值,但仍然需要查找 j j j,根源在于统计 j + 1 ⋯ i j+1 \cdots i j+1i 的价值需要同时用到两次更新的坐标位置。但其实我们可以把 + i , − j +i,-j +i,j 这两件事情分开做,若我们每次找最大的 d p j − j dp_j - j dpjj,那么在更新 i i i 时,我们可以直接将该结果 + i +i +i 便变成了 d p j + i − j dp_j + i - j dpj+ij,这样我们就可以分开统计,直接使用树状数组找最大的 d p j − j dp_j - j dpjj 就可以省去那一次找 j j j 的遍历,使得总体复杂度变为 O ( n log ⁡ n ) O(n \log n) O(nlogn)
  值得注意的是排序时,对于前缀和相等的情况,我们要把位置大的放前面,这样我们更新的时候就不会使用位置小的去更新位置大的了(此时该段的总价值为 0 0 0,我们更新的时候是直接 + i , − j +i, -j +i,j 的操作,没有考虑 0 0 0 的情况,会导致错误答案,而实际上根据第二段的分析,价值为 0 0 0 我们是不该考虑的)。以及要考虑没有分段的情况,即当前位置 i i i 的前缀和大于 0 0 0,此时最大价值总和就是 i i i

Code

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int maxn = 5e5 + 7;
int a[maxn], f[maxn], dp[maxn], ord[maxn], n;
LL p[maxn];
const int rinf = -0x7fffffff;

int lowbit(int x) { return (x & -x); }
void update(int x, int v) {
    while(x <= n) {
        f[x] = max(f[x], v);
        x += lowbit(x);
    }
}
int query(int x) {
    int ans = rinf;
    while(x > 0) {
        ans = max(f[x], ans);
        x -= lowbit(x);
    }
    return ans;
}


int main() {
    int T;
    cin >> T;
    
    while(T--) {
        cin >> n;
        for(int i=1; i<=n; ++i) f[i] = rinf;
        vector<pair<LL, int> > pre;

        for(int i=1; i<=n; ++i) {
            cin >> a[i]; p[i] = p[i-1] + a[i]; pre.push_back(make_pair(p[i], -i));
        }
        sort(pre.begin(), pre.end());
        for(int i=1; i<=n; ++i) ord[-pre[i-1].second] = i;

        for(int i=1; i<=n; ++i) {
            dp[i] = dp[i-1] + (a[i] > 0?1:(a[i] < 0? -1: 0));
            dp[i] = max(dp[i], query(ord[i]) + i);
            if(p[i] > 0) dp[i] = i;
            update(ord[i], dp[i] - i);
        }

        cout << dp[n] << endl;

        p[0] = 0; dp[0] = 0;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值