AtCoder Beginner Contest 353 abc353 G - Merchant Takahashi 题解

题意

AtCoder Beginner Contest 353 abc353 G - Merchant Takahashi

n(1n2e5) 个城镇和 m(1m2e5) 个活动依次发生,第 i(1im) 个活动在第 ti(1tin) 个城镇举办,你如果参加则可获得 pi(1pi1e13) 的受益。你从 i 城镇移动到 j 城镇需要花费 c|ij|,(1c1e9)。求最大受益。

思路

这道题能够轻松的写出 O(n2m) 的dp写法。设 dp[i][j] 为举行了前 i 个活动,并且最终在第 j 个城镇时候的最大受益。

dp[i][j]=d[i1][k]c|jk|+(j==ti)×pi

这里我们发现其实枚举第 i 个活动时候,只需要考虑最终在第 ti 个城镇就好。这时候复杂度就降为了 O(nm),方程变为了

dp[i][ti]=max1jn{dp[i1][j]c|jti|}+pi

优化第一维就变成了dp[ti]=max1jn{dp[j]c|jti|}+pi
但复杂度依旧很高,继续考虑,尝试把绝对值符号给拆开,方程变为

dp[ti]=max{max1jti{dp[j]c×(tij)},maxtijn{dp[j]c×(jti)}}+pi

dp[ti]=max{max1jti{dp[j]+cjcti},maxtijn{dp[j]cj+cti}}+pi

这里问题就变为了快速获取 [1,ti][ti,n] 中的最大值,而线段树可以完成这个事,我们可以建2个线段树来维护区间最大值。
一个树维护 max1jn{dp[j]+cj}
一个树维护 max1jn{dp[j]cj}
线段树的复杂度是 O(logn),最终整体复杂度为 O(mlogn)

代码

#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
#include <bitset>
#include <numeric>

#define fi first
#define se second
#define endl '\n'


using namespace std;
using ll = long long;
using ull = unsigned long long;
using pii = pair<int, int>;

const ll inf = 0x3f3f3f3f3f3f3f3f;
const ull P = 131;
const double eps = 1e-4;
const int N = 2e5 + 10;

// 线段树
struct SegmentTree {
    struct Node{
        int l, r;
        ll sum;
    };
    vector<Node> tr;

    void init(int n, vector<ll> &a) {
        tr.resize(n * 4 + 1);
        build(1, 1, n, a);
    }

    inline int lchild(int u) {
        return u << 1;
    }
    inline int rchild(int u) {
        return u << 1 | 1;
    }

    void pushup(int u) {
        tr[u].sum = max(tr[lchild(u)].sum, tr[rchild(u)].sum);
    }
    
    void build(int u, int l, int r, vector<ll> &a) {
        if(l == r) tr[u] = {l, r, a[l]};
        else {
            tr[u] = {l, r};
            int mid = (l + r) >> 1;
            build(lchild(u), l, mid, a), build(rchild(u), mid+1, r, a);
            pushup(u);
        }
    }

    void modify(int u, int x, ll v) {
        if(tr[u].l == tr[u].r) {
            tr[u].sum = max(tr[u].sum, v);
        }
        else {
            int mid = (tr[u].l + tr[u].r) >> 1;
            if(x <= mid) modify(lchild(u), x, v);
            else modify(rchild(u), x, v);
            pushup(u);
        }
    }

    ll query(int u, int l, int r){
        if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
        int mid = (tr[u].l + tr[u].r) >> 1;
        ll res = -inf;
        if(l <= mid) res = max(res, query(lchild(u), l, r));
        if(r > mid) res = max(res, query(rchild(u), l, r));
        return res;
    }
};

int n, m;
ll c;
pair<int, ll> mar[N];
ll f[N];

void solve() {
    scanf("%d%lld%d", &n, &c, &m);
    for (int i=1; i<=m; i++) {
        scanf("%d%lld", &mar[i].fi, &mar[i].se);
    }

    vector<ll> a(n + 1, -inf);
    SegmentTree tr_l, tr_r;
    a[1] = c;
    tr_l.init(n, a);
    a[1] = -c;
    tr_r.init(n, a);
    ll res = 0;
    for (int i=1; i<=m; i++) {
        auto [t, p] = mar[i];
        ll dp1 = tr_l.query(1, 1, t) - c * t;
        ll dp2 = tr_r.query(1, t, n) + c * t;
        f[i] = max(dp1, dp2) + p;
        res = max(res, f[i]);
        tr_l.modify(1, t, f[i] + c * t);
        tr_r.modify(1, t, f[i] - c * t);
    }

    printf("%lld\n", res);
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    // multiple case
    // int t; scanf("%d", &t);
    // while(t--) {
    //     solve();
    // }

    // single case
    solve();

    return 0;
}
posted @   1v7w  阅读(46)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
历史上的今天:
2023-05-17 AtCoder Beginner Contest 102 D - Equal Cut 题解
点击右上角即可分享
微信分享提示