【Codeforces】1668D Optimal Partition题解

题目大意

给定一个长度为 n n n的数组 a ( − 1 0 9 ≤ a i ≤ 1 0 9 ) a(-10^9 \le a_i \le 10^9) a(109ai109)
我们可以把它分割成任意个连续的子序列。
每个子序列 s k = a l . . . a r s_k = a_l...a_r sk=al...ar会有一个权值:

  • ∑ j = l r a i > 0 \sum_{j=l}^{r}a_i > 0 j=lrai>0,则权值为 r − l + 1 r - l + 1 rl+1
  • ∑ j = l r a i = 0 \sum_{j=l}^{r}a_i = 0 j=lrai=0,则权值为 0 0 0
  • ∑ j = l r a i > 0 \sum_{j=l}^{r}a_i > 0 j=lrai>0,则权值为 − ( r − l + 1 ) -(r - l + 1) (rl+1)

求出让所有子序列权值之和最大的一种分割方法。

题目链接

思路

我们可以先按照动态规划的思路来写出状态转移方程,设 f i f_i fi是在 i i i之前可以获得最大权值, c a l c ( i , j ) calc(i,j) calc(i,j)是计算相应子序列的权值。
那么:
f [ i ] = m a x ( f [ j ] + c a l c ( j + 1 , i ) ) , 1 ≤ j ≤ i f[i] = max(f[j] + calc(j + 1, i)),1 \le j \le i f[i]=max(f[j]+calc(j+1,i)),1ji
我们继续观察它,假如 a [ i ] a[i] a[i]加上前面的子序列都是负数,那么我们就完全可以让 a [ i ] a[i] a[i]单独成一个子序列,这样它对答案的减少只有 − 1 -1 1

也就是说,我们计算 a [ i ] a[i] a[i]时,我们只用考虑位置在它前面且前缀和小于它的位置

但是即使只考虑这些,我们需要遍历所有的 j , j < i j, j < i j,j<i还是会TLE : (

不过我们发现,如果我们只考虑在它前面且前缀和小于它的位置,那么上面状态转移方程可以化简为:
f [ i ] = m a x ( f [ j ] + ( i − j ) , 1 ≤ j ≤ i f[i] = max(f[j] + (i - j),1 \le j \le i f[i]=max(f[j]+(ij),1ji(因为我们只选择和为正数的子序列)
进一步推导发现 f [ i ] = m a x ( f [ j ] − j + i ) f[i] = max(f[j] - j + i) f[i]=max(f[j]j+i)
再往下化简一步:
f [ i ] = m a x ( f [ j ] − j ) + i , 1 ≤ j ≤ i f[i] = max(f[j] - j) + i,1 \le j \le i f[i]=max(f[j]j)+i,1ji

m a x ( f [ j ] − j ) max(f[j] - j) max(f[j]j)可以较快速的求出。

具体来说,我们用把前缀和sum数组排序,比如 s u m [ 2 ] sum[2] sum[2]排序后的位置是 4 4 4,那么我们更新 f [ 2 ] f[2] f[2]的时候就查看树状数组中 4 4 4之前的最大值。。插入 f [ 2 ] − 2 f[2] - 2 f[2]2时,就把放入树状数组中的位置 4 4 4

遍历的时候还是按照原来的顺序,这样既能按照动态规划的正确形式更新答案,也能正确维护树状数组。

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;

const int maxN = 5e5 + 78;
const long long INF = 0x7f7f7f7f;

int T, n, cnt, order[maxN];
long long a[maxN], f[maxN], c[maxN], sum[maxN];
pair<long long, int> t[maxN];

inline void add(int pos, long long val)
{
    for(; pos <= n; pos += (pos & -pos))
        c[pos] = max(c[pos], val);
}

inline long long ask(int pos)
{
    long long tmp = -INF;
    for(; pos > 0; pos -= (pos & -pos))
        tmp = max(tmp, c[pos]);
    return tmp;
}


int main()
{
    scanf("%d", &T);
    while(T--) {
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i) {
            scanf("%lld", &a[i]);
            sum[i] = sum[i - 1] + a[i];
            t[i] = {sum[i], -i};
        }
        for(int i = 1; i <= n; ++i) {
            c[i] = f[i] = -INF;
        }
        sort(t + 1, t + 1 + n);
        for(int i = 1; i <= n; ++i)
            order[-t[i].second] = i;
        for(int i = 1; i <= n; ++i) {
            f[i] = f[i -1] + (a[i] == 0 ? 0 : a[i] / abs(a[i]));
            f[i] = max(f[i], ask(order[i]) + i);
            if(sum[i] > 0)
                f[i] = i;
            add(order[i], f[i] - i);
        }
        printf("%lld\n", f[n]);
    }
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值