题面链接
有两种法术,法术类型为 0 0 0 的法术能造成伤害,法术类型为 1 1 1 的法术不仅能造成伤害,还能使下一次法术的伤害翻倍。
现在动态的获得/删除法术,请问对于每一次操作之后,你能造成的最大伤害是多少
对于一个动态的数列,其中有两种值,紧贴着 B B B 类型值之后的值翻倍,求问每个时刻整个数列的最大值。
考虑最优的情况,让每一个 B B B 类型的值之后的值是整个序列中最大的那 k k k 个值,即可。但是无论如何组合,第一个 B B B 类型的值必然不能翻倍,也就是一定存在一个 B B B 类型的值不能翻倍,那我们固定取最小的那个 B B B 类型的值,则题意可以转为如下
给出一个序列,起始的时候序列为空
每次将一个数值加入到序列中,或者从序列中删除一个数字
每次操作后,给出两个值: s , k s, k s,k,请从序列中选出 k k k 个值,在这 k k k 个值不包含 s s s 前提的和的最大值是多少
首先用一个 set
可以记录当前所有的 B B B 类型的值,则 set
的第一个值便是最小的不可选的值 s s s。
而后求前缀和,可以将当前时间的所有值之和求出,则答案只需要求出前 k k k 大且不包含 s s s 的和即可
求出前 k k k 大不难想到主席树,而主席树是可持久化线段树,在本题的在线做法中,并不需要保留历史版本,所以只需要普通的权值线段树即可。
建立一棵下标范围为 [ 1 , 1 e 9 ] [1, 1e9] [1,1e9] 的权值线段树,表示每个值出现的次数(虽然在本题中每个值仅出现一次),线段树上的每个节点维护了它的子树权值和与子树价值的加权和。
同时权值线段树也提供了求算 s s s 在整个序列中的位置,如果 s s s 在前 k k k 大中,则需要求算前 k + 1 k + 1 k+1 的解,并减去 s s s。如果不在则直接求解即可。
考虑到数值范围为 [ 1 , 1 e 9 ] [1, 1e9] [1,1e9] 所以采用动态开点的线段树即可
#include
using namespace std;
#define int long long
const int MAXN = 2e5 + 100;
struct SegTree {
int data[MAXN * 40];
int cnt[MAXN * 40];
int lSon[MAXN * 40], rSon[MAXN * 40];
int tot = 1;
inline void reset() {
tot = 0;
next();
}
inline int next() {
data[tot] = 0;
cnt[tot] = 0;
lSon[tot] = 0;
rSon[tot] = 0;
return tot++;
}
inline int lson(int k) {
if (!lSon[k]) lSon[k] = next();
return lSon[k];
}
inline int rson(int k) {
if (!rSon[k]) rSon[k] = next();
return rSon[k];
}
inline void up(int k) {
data[k] = (lSon[k] ? data[lSon[k]] : 0) + (rSon[k] ? data[rSon[k]] : 0);
cnt[k] = (lSon[k] ? cnt[lSon[k]] : 0) + (rSon[k] ? cnt[rSon[k]] : 0);
}
void insert(int value, int k = 0, int l = 1, int r = 1e9) {
if (l == r) {
data[k] = value;
cnt[k]++;
return;
}
int mid = (l + r) >> 1;
if (value <= mid)
insert(value, lson(k), l, mid);
else
insert(value, rson(k), mid + 1, r);
up(k);
}
/**
* 删除一个值,前提这个值得存在
* @param value
* @param k
* @param l
* @param r
*/
void erase(int value, int k = 0, int l = 1, int r = 1e9) {
if (l == r) {
cnt[k]--;
data[k] = 0;
return;
}
int mid = (l + r) >> 1;
if (value <= mid)
erase(value, lSon[k], l, mid);
else
erase(value, rSon[k], mid + 1, r);
up(k);
}
/**
* 查找一个值是第几大[0, n),前提这个值得存在
* @param value
* @param k
* @param l
* @param r
*/
int find(int value, int k = 0, int l = 1, int r = 1e9) {
if (l == r)
return 0;
int mid = (l + r) >> 1;
if (value <= mid)
return (rSon[k] ? cnt[rSon[k]] : 0) + find(value, lSon[k], l, mid);
else
return find(value, rSon[k], mid + 1, r);
}
int sum(int num, int k = 0) {
if (num == 0) return 0;
if (cnt[k] == num) return data[k];
int rCnt = min(num, rSon[k] ? cnt[rSon[k]] : 0), lCnt = num - rCnt;
return ((lSon[k] && lCnt) ? sum(lCnt, lSon[k]) : 0) + ((rSon[k] && rCnt) ? sum(rCnt, rSon[k]) : 0);
}
} segTree;
int solve(int k, int mi) {
int pos = segTree.find(mi);
if (pos < k)
return segTree.sum(k + 1) - mi;
return segTree.sum(k);
}
void solve() {
int n;
cin >> n;
segTree.reset();
set<int> pool;
for (int i = 0; i < n; ++i) {
int op, value;
cin >> op >> value;
if (value > 0)
segTree.insert(value);
else
segTree.erase(-value);
if (op == 1) {
if (value > 0)
pool.insert(value);
else
pool.erase(-value);
}
if (segTree.cnt[0] == 0) {
cout << 0 << endl;
continue;
}
if (pool.size() == segTree.cnt[0]) {
cout << segTree.data[0] * 2 - (*pool.begin()) << endl;
continue;
}
if (pool.size())
cout << solve(pool.size(), *pool.begin()) + segTree.data[0] << endl;
else
cout << segTree.data[0] << endl;
}
}
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
#ifdef ACM_LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
signed localTestCount = 1, localReadPos = cin.tellg();
char localTryReadChar;
do {
if (localTestCount > 20)
throw runtime_error("Check the stdin!!!");
auto startClockForDebug = clock();
solve();
auto endClockForDebug = clock();
cout << "Test " << localTestCount << " successful" << endl;
cerr << "Test " << localTestCount++ << " Run Time: "
<< double(endClockForDebug - startClockForDebug) / CLOCKS_PER_SEC << "s" << endl;
cout << "--------------------------------------------------" << endl;
} while (localReadPos != cin.tellg() && cin >> localTryReadChar && localTryReadChar != '$' &&
cin.putback(localTryReadChar));
#else
solve();
#endif
return 0;
}
考虑时间分治,对于所有出现的值,我们可以记录它出现的时间区间,然后将其保存在线段树上。
考虑每次需要取出前 k k k 大,所以我们进行如下处理:
线段树的每个节点保存了 4 4 4个值:mi
,ma
,data
,lazy
分别指代:mi
:此节点下所有子树中还需要 k k k 最小的值。ma
:此节点下所有子树中还需要 k k k 最大的值。data
:此节点已经得到的值的和,lazy
:懒惰标记——有多少个值未被下推至叶子节点。
其中 mi
表示这个节点还能否保存值,ma
表示还能否有值经过这个节点
对于 s s s,则计算出每个时间内哪个值不可以被选,则不让这个值在这些时间点被加入到线段树中即可。
#include
using namespace std;
#define int long long
const int MAXN = 2e5 + 100;
struct SegTree {
int mi[MAXN << 2], ma[MAXN << 2];
int data[MAXN << 2], lazy[MAXN << 2];
inline int lson(int k) { return k << 1; }
inline int rson(int k) { return (k << 1) | 1; }
inline void up(int k) {
mi[k] = min(mi[lson(k)], mi[rson(k)]);
ma[k] = max(ma[lson(k)], ma[rson(k)]);
}
inline void push(int k, int l, int r) {
if (l == r) return;
if (lazy[k] == 0) return;
mi[lson(k)] -= lazy[k];
mi[rson(k)] -= lazy[k];
ma[lson(k)] -= lazy[k];
ma[rson(k)] -= lazy[k];
data[lson(k)] += data[k];
data[rson(k)] += data[k];
lazy[lson(k)] += lazy[k];
lazy[rson(k)] += lazy[k];
data[k] = 0;
lazy[k] = 0;
}
void build(int k, int l, int r, const vector<int> &a) {
data[k] = 0;
lazy[k] = 0;
if (l == r) {
mi[k] = ma[k] = a[l];
return;
}
int mid = (l + r) >> 1;
build(lson(k), l, mid, a);
build(rson(k), mid + 1, r, a);
up(k);
}
void update(int value, int x, int y, int k, int l, int r) {
if (ma[k] <= 0) return;
if (x == l && y == r && mi[k]) {
lazy[k]++;
data[k] += value;
ma[k]--;
mi[k]--;
return;
}
push(k, l, r);
int mid = (l + r) >> 1;
if (y <= mid)
update(value, x, y, lson(k), l, mid);
else if (x > mid)
update(value, x, y, rson(k), mid + 1, r);
else {
update(value, x, mid, lson(k), l, mid);
update(value, mid + 1, y, rson(k), mid + 1, r);
}
up(k);
}
void build(int k, int l, int r) {
if (l == r) return;
push(k, l, r);
int mid = (l + r) >> 1;
build(lson(k), l, mid);
build(rson(k), mid + 1, r);
}
void print(int k, int l, int r, const vector<int> &tot) {
if (l == r) {
cout << data[k] + tot[l] << endl;
return;
}
int mid = (l + r) >> 1;
print(lson(k), l, mid, tot);
print(rson(k), mid + 1, r, tot);
}
} segTree;
void solve() {
int n;
cin >> n;
map<int, vector<pair<int, int>>> pool;
set<int> lightning;
vector<int> cnt(n + 1), tot(n + 1);
tot[0] = 0;
for (int i = 1; i <= n; ++i) {
int op, u;
cin >> op >> u;
auto iter = pool.find(abs(u));
if (iter == pool.end()) {
pool.insert({u, {}});
iter = pool.find(u);
}
if (u > 0) {
iter->second.push_back({i, n});
} else {
iter->second.back().second = i - 1;
}
if (op == 1) {
if (u > 0) lightning.insert(u);
else lightning.erase(-u);
}
if (!lightning.empty()) {
iter = pool.find(*lightning.begin());
iter->second.back().second = i - 1;
iter->second.push_back({i + 1, n});
}
cnt[i] = lightning.size();
tot[i] = tot[i - 1] + u;
}
segTree.build(1, 1, n, cnt);
auto iter = pool.rbegin();
while (iter != pool.rend()) {
for (auto item : iter->second)
if (item.first <= item.second) segTree.update(iter->first, item.first, item.second, 1, 1, n);
iter++;
}
segTree.build(1, 1, n);
segTree.print(1, 1, n, tot);
}
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
#ifdef ACM_LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
signed localTestCount = 1, localReadPos = cin.tellg();
char localTryReadChar;
do {
if (localTestCount > 20)
throw runtime_error("Check the stdin!!!");
auto startClockForDebug = clock();
solve();
auto endClockForDebug = clock();
cout << "Test " << localTestCount << " successful" << endl;
cerr << "Test " << localTestCount++ << " Run Time: "
<< double(endClockForDebug - startClockForDebug) / CLOCKS_PER_SEC << "s" << endl;
cout << "--------------------------------------------------" << endl;
} while (localReadPos != cin.tellg() && cin >> localTryReadChar && localTryReadChar != '$' &&
cin.putback(localTryReadChar));
#else
solve();
#endif
return 0;
}
考虑到主席树,所以就开了一大堆的空间
对比而言,两种方法的效率近乎相同
什么,你问我为什么我的 codeforces 和你的不太一样(Codeforces 伪客户端)