zoukankan      html  css  js  c++  java
  • [省选联考 2020 A/B 卷] 冰火战士(数据结构)

    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

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

  • 相关阅读:
    Fault-Tolerant Virtual Machine 论文笔记
    Google File System 论文笔记
    Amazon Aurora 论文笔记
    MATLAB入门学习(二):分支语句和编程设计
    MATLAB入门学习(一):基础准备
    矩阵连乘问题
    合并排序 java
    生产者与消费者 代码实现 java
    图的封装(C++)
    二叉树的封装
  • 原文地址:https://www.cnblogs.com/longdie/p/14607841.html
Copyright © 2011-2022 走看看