ABC314G Amulets

原题传送门

提供一个常数巨大的暴力数据结构做法。

分析

看到这种极值类问题,可以先想一想二分。由于这题中可以击败的怪物一定是一段连续的前缀,所以考虑二分这段前缀的长度。

由于本题中一道护身符免掉的是一种类型的怪物所造成的总伤害,所以考虑单个怪物的伤害是没有意义的。接下来我们只考虑某一区间中同一类型的怪物所造成的总伤害。

那么二分之后考虑如何判断可行性。显然根据贪心的原则,我们一定会优先考虑免掉 造成最多伤害的 怪物类型 所造成的 总伤害。也就是说,对于一段前缀,我们每次去询问出现在这段前缀中的 每种类型的怪物 造成的总伤害 的前 k 大的和。前 k 大问题,想到主席树。发现主席树的本质实际上是前缀线段树,正好与本题相像。接下来考虑主席树维护什么东西。

以下所说的前 k 大都指前 k 大的和。

直接讲可能不太好讲,所以我们先从权值线段树讲起。这题用到的权值线段树的本质上是一个桶,但是这个桶可以查询前 k 大。而一旦将他可持久化,那就是一个可查询任意历史状态中所有元素前 k 大的桶。那对于这题来说,任一时刻的桶里存的应当是所有出现了的类型的怪物的总伤害,而时刻就是输入每一个怪物的顺序,也就是我们加入每一个怪物的顺序。

接下来考虑如何维护主席树。显然,加入一个怪物时,其所属的类型的怪物的总伤害会增加,对于桶来说就是原本的值少了一个,加上后得到的值多了一个。那么到线段树上就是先删掉原本的值,再加上现在的值。这样我们就可以得到一棵前缀线段树 (可持久化桶)。其中每个 1n 之间的每个下标 i 对应一个前缀线段树的根, 其中保存了从 1i 中出现的每一种怪物类型的怪物的伤害和。 然后前 k 大就是平凡的。

那么这样就有了一棵前缀线段树。对于每一个 k[0,m],我们二分最多能击败多少怪物。那么由于击败的所有怪物一定处在一段连续的前缀中,所以我们的判定就是使用 k 道护身符能否走过一段特定长度的前缀。根据我们之前讲的贪心原则,我们一定免掉造成伤害最高的怪物类型。 那么考虑最多能免掉多少伤害,这其实就是前 k 大问题,线段树已经维护好了。有了这个值之后就可以算出走完这一段前缀最少会受到多少伤害,然后跟初始生命值比较一下即可判定。

总时间复杂度 O(m(logn)(logS)),其中 S 为值域大小。由于线段树维护的值域实在太大,导致 logS 也十分巨大,所以可能需要开个 O2 才能卡过。

代码

#pragma GCC optimize(2)
#include <iostream>
#include <set>
#define ll long long
using namespace std;
const ll N = 3e14;
int n, m, h;
int a[300005], b[300005];
int ptcnt[300005];
ll msum[300005];
int rt[600005];
ll S[300005];
set<int> st;
struct Persistent_Segment_Tree {
    struct node {
        int l, r, sm;
        ll smv;
    } T[31000005];
    int ncnt;
    void Build(int& o, ll l, ll r) noexcept {
        o = ++ncnt;
        T[o].sm += m;
        if (l == r) 
            return;
        Build(T[o].l, l, (l + r) >> 1);
    }
    void Insert(int p, int& q, ll l, ll r, ll x, int y) noexcept {
        q = ++ncnt;
        T[q] = T[p];
        T[q].sm += y;
        T[q].smv += y * x;
        if (l == r) 
            return;
        ll mid = (l + r) >> 1;
        (x <= mid) ? Insert(T[p].l, T[q].l, l, mid, x, y) : Insert(T[p].r, T[q].r, mid + 1, r, x, y);
    }
    ll Query(int o, ll l, ll r, int k) noexcept {
        if (!o) 
            return 0;
        if (l == r) 
            return l * k;
        ll mid = (l + r) >> 1;
        return (T[T[o].r].sm >= k) ? Query(T[o].r, mid + 1, r, k) : (T[T[o].r].smv + Query(T[o].l, l, mid, k - T[T[o].r].sm));
    }
} seg;
inline bool chk(int c, int k) noexcept {
    if (ptcnt[k] <= c) 
        return 1;
    ll tmp = seg.Query(rt[k], 0, N, c);
    ll dt = S[k] - tmp;
    return (dt < h);
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m >> h;
    seg.Build(rt[0], 0, N);
    for (int i = 1; i <= n; i++) {
        cin >> a[i] >> b[i];
        S[i] = S[i - 1] + a[i];
        st.insert(b[i]);
        ptcnt[i] = st.size();
        seg.Insert(rt[i - 1], rt[i + n], 0, N, msum[b[i]], -1);
        msum[b[i]] += a[i];
        seg.Insert(rt[i + n], rt[i], 0, N, msum[b[i]], 1);
    }
    for (int i = 0; i <= m; i++) {
        int l = 0, r = n, ans = 0, mid;
        while (l <= r) {
            mid = (l + r) >> 1;
            if (chk(i, mid)) 
                ans = mid, l = mid + 1;
            else 
                r = mid - 1;
        }
        cout << ans << " ";
    }
    return 0;
}
posted @   forgotmyhandle  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示