Codeforces Round #426(Div.2)D题 DP+线段树

题意:

题目链接:http://codeforces.com/contest/834/problem/D
给出n个数,分割成k个区间,每个区间的价值为该区间内不同数字的种类数,问分割之后,所有区间最大价值和是多少。


思路:

很容易得到方程:
dp[i][j]=max(dp[i-1][k]+kind(k+1,j)
其中dp[i][j]表示的是到第j个数为止已经分了i组的最大价值,k的变化范围是[i,j](想想就能明白),这方程要直接计算复杂度肯定不够。
这里需要用到线段树来优化,思考之后可以发现,当遍历到a[j]的时候,若a[j]上一次出现的位置是last[a[j]],那么a[j]会给起点为[last[a[j]] + 1,j],终点为j的每个区间都增加1的贡献,这一步可以利用线段树区间更新,若线段树上初始存的是i-1轮的dp结果,然后从左到右遍历的过程中每次将新的贡献更新到线段树上,就可以维护max(dp[i-1][k]+kind(k+1,j)
,直接query(i,j)即可得到dp[i][j]。
需要注意的地方是:每次更新贡献区间是[last[a[j]] + 1,j],但这贡献是属于last[a[j]到j-1的dp值,所以要更新的是[last[a[j]],j-1]。并且为了不让线段树区间从0开始,这里可以把last初值设置成1,对结果没有影响。


代码:

#include <bits/stdc++.h>
using namespace std;
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
const int MAXN = 35005;

int a[MAXN], dp[55][MAXN], last[MAXN];
int maxx[MAXN << 2], lazy[MAXN << 2];

void pushup(int rt) {
    maxx[rt] = max(maxx[rt << 1], maxx[rt << 1 | 1]);
}

void pushdown(int rt, int len) {
    if (lazy[rt]) {
        lazy[rt << 1] += lazy[rt];
        lazy[rt << 1 | 1] += lazy[rt];
        maxx[rt << 1] += lazy[rt];
        maxx[rt << 1 | 1] += lazy[rt];
        lazy[rt] = 0;
    }
}


void update(int s, int e, int z, int l, int r, int rt) {
    if (s <= l && r <= e) {
        maxx[rt] += z;
        lazy[rt] += z;
        return;
    }
    pushdown(rt, r - l + 1);
    int m = (l + r) >> 1;
    if (s <= m) update(s, e, z, lson);
    if (e > m) update(s, e, z, rson);
    pushup(rt);
}

int query(int s, int e, int l, int r, int rt) {
    if (s <= l && r <= e)
        return maxx[rt];
    pushdown(rt, r - l + 1);
    int m = (l + r) >> 1;
    int ret = 0;
    if (s <= m) ret = max(ret, query(s, e, lson));
    if (e > m) ret = max(ret, query(s, e, rson));
    return ret;
}

bool vis[MAXN];

int main() {
   // freopen("in.txt", "r", stdin);
    int n, k;
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    int kind = 0;
    memset(vis, false, sizeof(vis));
    for (int i = 1; i <= n; i++) {
        if (!vis[a[i]]) ++kind;
        dp[1][i] = kind;
        vis[a[i]] = true;
    }
    for (int i = 2; i <= k; i++) {
        memset(lazy, 0, sizeof(lazy));
        memset(maxx, 0, sizeof(maxx));
        for (int j = 1; j <= n; j++) {
            update(j, j, dp[i - 1][j], 1, n, 1);
            last[a[j]] = 1;
        }
        for (int j = 1; j < i; j++) last[a[j]] = j;
        for (int j = i; j <= n; j++) {
            //printf("(%d, %d)\n", last[a[j]] + 1, j);
            update(last[a[j]], j - 1, 1, 1, n, 1);
            dp[i][j] = query(i - 1, j - 1, 1, n, 1);
            last[a[j]] = j;
        }
    }
    printf("%d\n", dp[k][n]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值