ABC314G Amulets
提供一个常数巨大的暴力数据结构做法。
分析
看到这种极值类问题,可以先想一想二分。由于这题中可以击败的怪物一定是一段连续的前缀,所以考虑二分这段前缀的长度。
由于本题中一道护身符免掉的是一种类型的怪物所造成的总伤害,所以考虑单个怪物的伤害是没有意义的。接下来我们只考虑某一区间中同一类型的怪物所造成的总伤害。
那么二分之后考虑如何判断可行性。显然根据贪心的原则,我们一定会优先考虑免掉 造成最多伤害的 怪物类型 所造成的 总伤害。也就是说,对于一段前缀,我们每次去询问出现在这段前缀中的 每种类型的怪物 造成的总伤害 的前 \(k\) 大的和。前 \(k\) 大问题,想到主席树。发现主席树的本质实际上是前缀线段树,正好与本题相像。接下来考虑主席树维护什么东西。
以下所说的前 \(k\) 大都指前 \(k\) 大的和。
直接讲可能不太好讲,所以我们先从权值线段树讲起。这题用到的权值线段树的本质上是一个桶,但是这个桶可以查询前 \(k\) 大。而一旦将他可持久化,那就是一个可查询任意历史状态中所有元素前 \(k\) 大的桶。那对于这题来说,任一时刻的桶里存的应当是所有出现了的类型的怪物的总伤害,而时刻就是输入每一个怪物的顺序,也就是我们加入每一个怪物的顺序。
接下来考虑如何维护主席树。显然,加入一个怪物时,其所属的类型的怪物的总伤害会增加,对于桶来说就是原本的值少了一个,加上后得到的值多了一个。那么到线段树上就是先删掉原本的值,再加上现在的值。这样我们就可以得到一棵前缀线段树 (可持久化桶)。其中每个 \(1\) 到 \(n\) 之间的每个下标 \(i\) 对应一个前缀线段树的根, 其中保存了从 \(1\) 到 \(i\) 中出现的每一种怪物类型的怪物的伤害和。 然后前 k 大就是平凡的。
那么这样就有了一棵前缀线段树。对于每一个 \(k \in [0, m]\),我们二分最多能击败多少怪物。那么由于击败的所有怪物一定处在一段连续的前缀中,所以我们的判定就是使用 \(k\) 道护身符能否走过一段特定长度的前缀。根据我们之前讲的贪心原则,我们一定免掉造成伤害最高的怪物类型。 那么考虑最多能免掉多少伤害,这其实就是前 k 大问题,线段树已经维护好了。有了这个值之后就可以算出走完这一段前缀最少会受到多少伤害,然后跟初始生命值比较一下即可判定。
总时间复杂度 \(O(m (\log n)(\log S))\),其中 \(S\) 为值域大小。由于线段树维护的值域实在太大,导致 \(\log S\) 也十分巨大,所以可能需要开个 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;
}

浙公网安备 33010602011771号