【题解】CF#833 B-The Bakery

本文探讨了在动态规划中使用线段树进行优化的方法,通过实例详细讲解了如何利用线段树来提高DP算法的效率,尤其是在区间贡献和最大值查找上的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  一个非常明显的 \(nk\) dp 状态 \(f[i][k]\) 表示以 \(i\) 为第 \(k\) 段的最后一个元素时所能获得的最大代价。转移的时候枚举上一段的最后一个元素 \(j\)更新状态即可。考虑如何优化这个过程?主要的时间消耗在两个部分:一个是确定一段区间的贡献,另一个是找到最大的值。

  这两个都是可以使用线段树来维护的,一段区间的贡献我们可以扫描线,而最大值则直接线段树维护最大值。如何滚动反而好像是最难的……想了一会儿,因为显然 memset 不可接受,然而我们可以 \(O(n)\) 建树啊……简直对自己的zz无语惹~

#include <bits/stdc++.h>
using namespace std;
#define maxn 2000000
#define maxm 40000
int n, K, ans, a[maxn], rec[maxn], last[maxn];
int f[maxm][55];

int read()
{
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * k;
}

struct Segament_Tree
{
    int mx[maxn], mark[maxn];
    
    void Push_Down(int p)
    {
        mark[p << 1] += mark[p], mark[p << 1 | 1] += mark[p];
        mx[p << 1] += mark[p], mx[p << 1 | 1] += mark[p];
        mark[p] = 0; 
    }
    
    void Build(int p, int l, int r, int K)
    {
        if(l == r) { mark[p] = 0, mx[p] = f[l - 1][K]; return; }
        int mid = (l + r) >> 1;
        mark[p] = 0; 
        Build(p << 1, l, mid, K), Build(p << 1 | 1, mid + 1, r, K);
        mx[p] = max(mx[p << 1], mx[p << 1 | 1]); 
    }
    
    void Update(int p, int l, int r, int L, int R, int x)
    {
        if(L <= l && R >= r) { mx[p] += x, mark[p] += x; return; }
        if(L > r || R < l) return;
        int mid = (l + r) >> 1;
        Push_Down(p);
        Update(p << 1, l, mid, L, R, x);
        Update(p << 1 | 1, mid + 1, r, L, R, x);
        mx[p] = max(mx[p << 1], mx[p << 1 | 1]);
    }
    
    int Query(int p, int l, int r, int L, int R)
    {
        if(L <= l && R >= r) { return mx[p]; }
        if(L > r || R < l) return 0;
        int mid = (l + r) >> 1;
        Push_Down(p);
        return max(Query(p << 1, l, mid, L, R), Query(p << 1 | 1, mid + 1, r, L, R));
    }
}T[2];

int main()
{
    n = read(), K = read();
    for(int i = 1; i <= n; i ++)
    {
        a[i] = read();
        last[i] = rec[a[i]], rec[a[i]] = i;
    }
    int now = 1, pre = 0;
    for(int j = 1; j <= K; j ++)
    {
        for(int i = 1; i <= n; i ++)
        {
            T[pre].Update(1, 1, n, last[i] + 1, i, 1);
            f[i][j] = T[pre].Query(1, 1, n, 1, i);
        }
        T[now].Build(1, 1, n, j);
        swap(now, pre);
    }
    printf("%d\n", f[n][K]);
    return 0;
}

 

转载于:https://www.cnblogs.com/twilight-sx/p/9822942.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值