opening
来做做去年的省选题,T1确实是个馺嬖题,不过想出不卡常的正解还是比较难的。
outline
一场比赛即将开始。
每位战士有两个属性:温度和能量,有两派战士:冰系战士的技能会对周围造成降温冰冻伤害,因而要求场地温度不低于他的自身温度才能参赛;火系战士的技能会对周围造成升温灼烧伤害,因而要求场地温度不高于他的自身温度才能参赛。
当场地温度确定时,双方能够参赛的战士分别排成一队。冰系战士按自身温度从低到高排序,火系战士按自身温度从高到低排序,温度相同时能量大的战士排在前面。首先,双方的第一位战士之间展开战斗,两位战士消耗相同的能量,能量少的战士将耗尽能量退出比赛,而能量有剩余的战士将继续和对方的下一位战士战斗(能量都耗尽则双方下一位战士之间展开战斗)。如此循环,直至某方战士队列为空,比赛结束。
你需要寻找最佳场地温度:使冰火双方消耗总能量最高的温度的最高值。
现在,比赛还处于报名阶段,目前还没有任何战士报名,接下来你将不断地收到报名信息和撤回信息。其中,报名信息包含报名战士的派系和两个属性,撤回信息包含要撤回的报名信息的序号。每当报名情况发生变化(即收到一条信息)时,你需要立即报出当前局面下的最佳场地温度,以及该场地温度下双方消耗的总能量之和是多少。若当前局面下无论何种温度都无法开展比赛(某一方没有战士能参赛),则只要输出 Peace。
数据范围 (2e6)。
solution
确实看上去题目挺复杂,不过随便转化一下发现挺三分的,不过不知道符合不符合条件,不过再一想发现可以用线段树维护单点值,然后直接二分就可以,复杂度 (Theta(nlog^2n))。
感觉只能拿60分,不过听神犇 (thesure) 说,改成树状数组再卡卡常就A了,不过听说 神犇中的神犇 (lin4xu) 直接线段树 A了,令人%%%。
回到这道题,发现需要一个 (nlog n) 的算法,于是你的线段树的每个叶子节点表示前缀和,然后就可以线段树上二分了,复杂度 (nlog n),不过因为常数过大,所以跑不过去,但
是如果你会树状数组上二分的话或者zkw线段树,那么你就A了它,所以这道题就完了,我在下面说一下树状数组二分的注意事项。
std
// by longdie
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define rint register int
using namespace std;
inline int read(int s = 0, int f = 1, char ch = getchar()) {
while(!isdigit(ch)) { if(ch == '-') f = -1; ch = getchar(); }
while(isdigit(ch)) { s = s*10 + ch - '0', ch = getchar(); }
return s * f;
}
const int N = 2e6 + 5;
int m, b[N], tot, c1[N], c2[N], sum;
struct Q {
int op, id, x, y;
} q[N];
inline int lowbit(int x) { return x & -x; }
inline void add(int x, int y, int *c) {
for(; x <= tot; x += lowbit(x)) c[x] += y;
}
inline int get(int x, int *c, int res = 0) {
for(; x; x -= lowbit(x)) res += c[x];
return res;
}
inline void change(int id, int t, int val) {
if(!id) add(t, val, c1);
else add(t, val, c2), sum += val;
}
inline void query() {
int pos = 0, res1 = 0, res2 = 0, ans = 0, p = 0;
for(rint j = 20, x; j >= 0; --j) {
x = pos + (1 << j);
if(x > tot) continue;
int tmp1 = res1 + c1[x], tmp2 = res2 + c2[x];
if(tmp1 <= sum - tmp2) res1 = tmp1, res2 = tmp2, pos = x;
}
ans = min(res1, sum - res2), p = pos;
++pos;
int flag = 0;
res1 = get(pos, c1), res2 = sum - get(pos, c2);
if(min(res1, res2) >= ans) ans = min(res1, res2), p = pos, flag = 1;
if(flag) {
pos = 0, res1 = res2 = 0;
for(rint j = 20; j >= 0; --j) {
int x = pos + (1 << j);
if(x > tot) continue;
int tmp1 = res1 + c1[x], tmp2 = res2 + c2[x];
if(x <= p) { pos = x, res1 = tmp1, res2 = tmp2; continue; }
if(min(tmp1, sum-tmp2) >= ans) {
pos = x, res1 = tmp1, res2 = tmp2; continue;
}
}
p = pos;
}
if(ans == 0) puts("Peace");
else printf("%d %d
", b[p+1]-1, ans*2);
}
signed main() {
m = read();
for(rint i = 1; i <= m; ++i) {
q[i].op = read();
if(q[i].op == 2) q[i].x = read();
else q[i].id = read(), q[i].x = read() + (q[i].id == 1), q[i].y = read(), b[++tot] = q[i].x;
}
sort(b + 1, b + tot + 1), tot = unique(b + 1, b + tot + 1) - b - 1;
for(rint i = 1; i <= m; ++i) {
if(q[i].op == 1) {
q[i].x = lower_bound(b + 1, b + tot + 1, q[i].x) - b;
change(q[i].id, q[i].x, q[i].y);
}
else {
int k = q[i].x; change(q[k].id, q[k].x, -q[k].y);
}
query();
}
return 0;
}
end
树状数组上二分,就是从大到小开始跳就可以了,其实就是个二分了。