[省选联考 2020 A/B 卷] 冰火战士(数据结构)

opening

题目链接

来做做去年的省选题,T1确实是个馺嬖题,不过想出不卡常的正解还是比较难的。

outline

一场比赛即将开始。

每位战士有两个属性:温度和能量,有两派战士:冰系战士的技能会对周围造成降温冰冻伤害,因而要求场地温度不低于他的自身温度才能参赛;火系战士的技能会对周围造成升温灼烧伤害,因而要求场地温度不高于他的自身温度才能参赛。

当场地温度确定时,双方能够参赛的战士分别排成一队。冰系战士按自身温度从低到高排序,火系战士按自身温度从高到低排序,温度相同时能量大的战士排在前面。首先,双方的第一位战士之间展开战斗,两位战士消耗相同的能量,能量少的战士将耗尽能量退出比赛,而能量有剩余的战士将继续和对方的下一位战士战斗(能量都耗尽则双方下一位战士之间展开战斗)。如此循环,直至某方战士队列为空,比赛结束。

你需要寻找最佳场地温度:使冰火双方消耗总能量最高的温度的最高值。

现在,比赛还处于报名阶段,目前还没有任何战士报名,接下来你将不断地收到报名信息和撤回信息。其中,报名信息包含报名战士的派系和两个属性,撤回信息包含要撤回的报名信息的序号。每当报名情况发生变化(即收到一条信息)时,你需要立即报出当前局面下的最佳场地温度,以及该场地温度下双方消耗的总能量之和是多少。若当前局面下无论何种温度都无法开展比赛(某一方没有战士能参赛),则只要输出 Peace。

数据范围 \(2e6\)

solution

确实看上去题目挺复杂,不过随便转化一下发现挺三分的,不过不知道符合不符合条件,不过再一想发现可以用线段树维护单点值,然后直接二分就可以,复杂度 \(\Theta(n\log^2n)\)

感觉只能拿60分,不过听神犇 \(thesure\) 说,改成树状数组再卡卡常就A了,不过听说 神犇中的神犇 \(lin4xu\) 直接线段树 A了,令人%%%。

回到这道题,发现需要一个 \(n\log n\) 的算法,于是你的线段树的每个叶子节点表示前缀和,然后就可以线段树上二分了,复杂度 \(n\log 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\n", 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

树状数组上二分,就是从大到小开始跳就可以了,其实就是个二分了。

posted @ 2021-04-01 19:48  longdie  阅读(128)  评论(0编辑  收藏  举报