Codeforces Round #721 (Div. 2) E - Partition Game 线段树优化dp

Codeforces Round #721 Div. 2 E - Partition Game 线段树优化dp


传送门: https://codeforces.com/contest/1527/problem/E

当 时 E 题 没 有 时 间 写 , 但 是 20 分 钟 足 够 了 。 当时E题没有时间写,但是20分钟足够了。 E20

题意

将 一 个 长 为 n 的 序 列 , 分 成 k 段 。 将一个长为n的序列,分成k段。 nk
在 每 段 中 , 对 于 每 个 数 字 来 说 , 贡 献 为 最 后 一 次 出 现 的 位 置 减 第 一 次 出 现 的 位 置 , 即 l a s t ( x ) − f i r s t ( x ) 在每段中,对于每个数字来说,贡献为最后一次出现的位置减第一次出现的位置,即last(x)-first(x) last(x)first(x)

求 分 成 k 段 后 , 最 小 的 总 贡 献 。 求分成k段后,最小的总贡献。 k

思路

n 分 成 k 段 , 再 看 看 数 据 范 围 , n ≤ 35000 , k ≤ 100 , d p [ n ] [ k ] n分成k段,再看看数据范围,n\leq 35000,k\leq 100,dp[n][k] nkn35000k100dp[n][k]

d p [ n ] [ k ] 表 示 前 n 个 数 字 分 成 k 段 后 的 最 小 总 贡 献 。 dp[n][k]表示前n个数字分成k段后的最小总贡献。 dp[n][k]nk
那 么 转 移 方 程 就 非 常 好 写 : 那么转移方程就非常好写:

d p [ i ] [ j ] = d p [ i − 1 ] [ k ] + v a l [ k + 1 ] [ j ] dp[i][j]=dp[i-1][k]+val[k+1][j] dp[i][j]=dp[i1][k]+val[k+1][j]
v a l [ i ] [ j ] 表 示 [ i , j ] 内 所 有 数 字 的 贡 献 。 val[i][j]表示[i,j]内所有数字的贡献。 val[i][j][i,j]
很 容 易 推 出 k ∈ [ i − 1 , j − 1 ] 很容易推出k \in [i-1,j-1] k[i1,j1]

这 样 我 们 的 复 杂 度 为 O ( n 2 k ) , 是 不 行 的 , 不 过 O ( n k ) 是 可 以 的 , 所 以 我 们 想 办 法 消 掉 一 个 n 。 这样我们的复杂度为O(n^2k),是不行的,不过O(nk)是可以的,所以我们想办法消掉一个n。 O(n2k)O(nk)n

因 为 我 们 的 d p [ i ] [ j ] 都 是 由 d p [ i − 1 ] [ j − 1 ] 推 出 来 的 。 因为我们的dp[i][j]都是由dp[i-1][j-1]推出来的。 dp[i][j]dp[i1][j1]
而 我 们 的 目 的 就 是 找 到 一 个 k 使 得 d p [ i − 1 ] [ k ] + v a l [ k + 1 ] [ j ] 最 小 , 而 k ∈ [ i − 1 , j − 1 ] . 而我们的目的就是找到一个k使得dp[i-1][k]+val[k+1][j]最小,而k \in [i-1,j-1]. k使dp[i1][k]+val[k+1][j]k[i1,j1].
进 而 我 们 可 以 用 线 段 树 围 绕 k 维 护 区 间 最 小 值 , 这 样 我 们 可 以 在 l o g 内 找 到 这 样 的 k 进 行 转 移 。 进而我们可以用线段树围绕k维护区间最小值,这样我们可以在log内找到这样的k进行转移。 线klogk

假 设 我 们 当 前 要 将 第 j 个 数 字 分 类 , 则 [ 1 , 前 一 个 位 置 − 1 ] 的 贡 献 都 要 改 变 , 变 多 少 呢 ? 多 j − ( 前 一 个 位 置 ) 。 假设我们当前要将第j个数字分类,则[1,前一个位置-1]的贡献都要改变,变多少呢?多j-(前一个位置)。 j[1,1]j()

也 就 是 修 改 : 也就是修改:

if(from[j] != -1) modify(1, 1, from[j] - 1, j - from[j]);

查 询 是 在 [ i − 1 , j − 1 ] 找 , 也 就 是 : 查询是在[i-1,j-1]找,也就是: [i1,j1]

dp[j][i] = min(dp[j - 1][i - 1], query(1, i - 1, j - 1));

最 终 时 间 复 杂 度 为 O ( n k log ⁡ n ) 最终时间复杂度为O(nk\log n) O(nklogn)

Code

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

const ll INF = 0x3f3f3f3f;

#define lc u << 1
#define rc u << 1 | 1
#define mid (t[u].l + t[u].r) / 2

const int N = 3e5 + 10;
const int K = 100 + 10;
int dp[N][K];

struct Tree {
    int l, r;
    int mn;
    int tag;
}t[N << 1];

inline void push_up(int u) {
    t[u].mn = min(t[lc].mn, t[rc].mn);
}

inline void push_down(int u) {
    if(!t[u].tag) return ;
    t[lc].tag += t[u].tag;
    t[rc].tag += t[u].tag;
    t[lc].mn  += t[u].tag;
    t[rc].mn  += t[u].tag;
    t[u].tag = 0;
}

void build(int u, int l, int r, int k) {
    t[u].l = l; t[u].r = r;
    t[u].tag = 0; t[u].mn = INF;
    if(l == r) {
        t[u].mn = dp[l][k];
        return ;
    }
    int m = (l + r) / 2;
    build(lc, l, m, k);
    build(rc, m + 1, r, k);
    push_up(u);
}

void modify(int u, int ql, int qr, int val) {
    if(ql <= t[u].l && t[u].r <= qr) {
        t[u].mn += val;
        t[u].tag += val;
        return ;
    }
    push_down(u);
    if(ql <= mid) modify(lc, ql, qr, val);
    if(qr >  mid) modify(rc, ql, qr, val);
    push_up(u);
}

int query(int u, int ql, int qr) {
    if(ql <= t[u].l && t[u].r <= qr) return t[u].mn;
    push_down(u);
    int ans = INF;
    if(ql <= mid) ans = min(ans, query(lc, ql, qr));
    if(qr  > mid) ans = min(ans, query(rc, ql, qr));
    return ans;
}


void solve() {
    int n, k; cin >> n >> k;
    vector<int> a(n + 1), pre(n + 1, -1), from(n + 1);
    for(int i = 1;i <= n; i++) cin >> a[i];

    for(int i = 1;i <= n; i++) {
        dp[i][1] = dp[i - 1][1];
        from[i] = pre[a[i]];
        if(from[i] != -1) dp[i][1] += i - from[i];
        pre[a[i]] = i;
    }

    for(int i = 2;i <= k; i++) {
        build(1, 1, n, i - 1);
        for(int j = i;j <= n; j++) {
            if(from[j] != -1) modify(1, 1, from[j] - 1, j - from[j]);
            dp[j][i] = min(dp[j - 1][i - 1], query(1, i - 1, j - 1));
            // cout << j << " " << "分成" << i << "段:" << " " << dp[j][i] << endl;
        }
    }
    cout << dp[n][k] << endl;
}

signed main() {
    solve();
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值