P7214 [JOISC2020] 治療計画 题解

这题要的是最优解,我们首先观察出来一些性质:如果在某次治疗时,存在健康人的极长区间被这次治疗完全包含,那么这个健康人区间对应的治疗是无用的,可以去掉。

这个性质比较显然。它有一个也很显然的推论:最优解中,在某次治疗时,如果治疗区间内部存在健康人区间,那么这个区间要么会向左延伸到治疗区间以外,要么会向右延伸到治疗区间以外。

所以,当加入一个治疗方案时,它会把至多两个健康人区间合并在一起。

而我们可以发现,健康人区间的边界只会与某个治疗方案有关(左右边界所属的治疗方案可能不同)。我们按时间顺序扫过来,记录健康人区间的边界所属的治疗方案,就可以判断区间的合并了。我们的最终目标就是要在某个时刻让健康人区间的左右端点分别为 1 , n 1,n 1,n

进一步地,判断合并的区间其实不需要按照时间顺序。只要以任何顺序加入治疗方案,不断合并区间,最终合并出来 ( 1 , n ) (1,n) (1,n) 即可。

所以我们甚至可以从左到右扫区间。那么这样我们考虑这样一个最短路(dp)建图。所有左端点为 1 1 1 的治疗方案 i i i 是起点,距离就是其代价 C i C_i Ci

从点 i i i 到点 j j j 有边,当且仅当:

T j ≥ T i T_j \geq T_i TjTi R i − T j + T i ≥ L j − 1 R_i-T_j+T_i \geq L_j-1 RiTj+TiLj1 或者

T j < T i T_j<T_i Tj<Ti L j − T j + T i ≤ R i + 1 L_j-T_j+T_i \leq R_i+1 LjTj+TiRi+1

边权就是 C j C_j Cj

跑完最短路后,对于所有右端点为 n n n 的治疗方案,将他们的最短路取个 m i n min min 就是答案了。

考虑优化,显然可以按 T i T_i Ti 的顺序建出两棵可持久化线段树,然后在可持久化线段树上连边、跑最短路即可。

时间复杂度 O ( m log ⁡ 2 m ) O(m \log^2 m) O(mlog2m)

AC code:

#include <bits/stdc++.h>
using namespace std;
const int M = 100005;
int n, m, T[M], L[M], R[M], C[M], id[M];
priority_queue<pair<long long, int>, vector<pair<long long, int> >, greater<pair<long long, int> > > que;
vector<int> v1, v2;
vector<pair<int, int> > G[M * 37];
long long dist[M * 37];
inline void add_edge(int u, int v, int w) {G[u].emplace_back(v, w);}
struct Node {int lson, rson;} tr[M * 18 * 2];
int tot, root2[M], root1[M];
inline void Ins(int &x, int y, int l, int r, int pos, int id) {
    x = ++tot;
    tr[x] = tr[y];
    if (y) add_edge(x + m, y + m, 0);
    if (l == r) {
        add_edge(x + m, id, C[id]);
        return;
    }
    int mid = (l + r) >> 1;
    if (pos <= mid) Ins(tr[x].lson, tr[y].lson, l, mid, pos, id), add_edge(x + m, tr[x].lson + m, 0);
    else Ins(tr[x].rson, tr[y].rson, mid + 1, r, pos, id), add_edge(x + m, tr[x].rson + m, 0);
}
inline void Add(int x, int l, int r, int lf, int rg, int id) {
    if (!x) return;
    if (lf <= l && r <= rg) {
        add_edge(id, x + m, 0);
        return;
    }
    int mid = (l + r) >> 1;
    if (lf <= mid) Add(tr[x].lson, l, mid, lf, rg, id);
    if (rg > mid) Add(tr[x].rson, mid + 1, r, lf, rg, id);
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        scanf("%d%d%d%d", &T[i], &L[i], &R[i], &C[i]);
        v1.push_back(L[i] + T[i] - 1);
        v2.push_back(L[i] - T[i] - 1);
        id[i] = i;
    }
    sort(id + 1, id + 1 + m, [](int i, int j) {
        return T[i] < T[j];
    });
    sort(v1.begin(), v1.end());
    v1.resize(unique(v1.begin(), v1.end()) - v1.begin());
    sort(v2.begin(), v2.end());
    v2.resize(unique(v2.begin(), v2.end()) - v2.begin());
    root2[0] = 0;
    for (int i = 1; i <= m; i++) {
        int j = id[i];
        int p = lower_bound(v2.begin(), v2.end(), L[j] - T[j] - 1) - v2.begin() + 1;
        Ins(root2[i], root2[i - 1], 1, m, p, j);
    }
    root1[m + 1] = 0;
    for (int i = m; i; i--) {
        int j = id[i];
        int p = lower_bound(v1.begin(), v1.end(), L[j] + T[j] - 1) - v1.begin() + 1;
        Ins(root1[i], root1[i + 1], 1, m, p, j);
    }
    for (int i = 1, k = 1; i <= m; i++) {
        while (k <= m && T[id[i]] >= T[id[k]]) k++;
        int j = id[i];
        int p = upper_bound(v2.begin(), v2.end(), R[j] - T[j]) - v2.begin();
        if (1 <= p) Add(root2[k - 1], 1, m, 1, p, j);
    }
    for (int i = m, k = m; i; i--) {
        while (k > 0 && T[id[i]] <= T[id[k]]) k--;
        int j = id[i];
        int p = upper_bound(v1.begin(), v1.end(), R[j] + T[j]) - v1.begin();
        if (1 <= p) Add(root1[k + 1], 1, m, 1, p, j);
    }
    memset(dist, 0x3f, sizeof(dist));
    for (int i = 1; i <= m; i++) {
        if (L[i] == 1) {
            dist[i] = C[i];
            que.push(make_pair(dist[i], i));
        }
    }
    while (!que.empty()) {
        auto P = que.top(); que.pop();
        int u = P.second;
        if (dist[u] != P.first) continue;
        for (auto &e : G[u]) {
            int v = e.first;
            if (dist[v] > dist[u] + e.second) {
                dist[v] = dist[u] + e.second;
                que.push(make_pair(dist[v], v));
            }
        }
    }
    long long ans = 1ll << 55;
    for (int i = 1; i <= m; i++) {
        if (R[i] == n) ans = min(ans, dist[i]);
    }
    if (ans >= (1ll << 55)) ans = -1;
    printf("%lld\n", ans);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值