• 【题解】 「联合省选2020」冰火战士 线段树+二分 LOJ3299


    Legend

    Link to LOJ

    维护两个二元组集合 \(A,B\),支持操作 \(Q\ (1 \le Q \le 2\times10^6)\) 次:

    • 向其中某个集合插入一个二元组 \((t,e) \ (1 \le t \le 2\times 10^9,\sum e \le 2 \times 10^9)\)
    • 删除之前插入的一个二元组。

    每次操作后都会询问:

    • 对于给定的 \(mid\) ,定义 \(Ans=\min\left(\sum\limits _{a\in A}[a.t\le mid]\times a.e,\sum\limits_{b\in B} [b.t \ge mid]\times b.e\right)\)

    • 请给出能取到最大的 \(Ans\times 2\) 的值 ,以及取到最大 \(Ans\) 时的最大 \(mid\)

    时空 \(\textrm{3s/512MB}\)

    Editorial

    作为 联合省选\(2020\) 的第一题,必定是一道良心送温暖题,但大力的卡常似乎让它失去了应有的效果,让我们一起为出题人点踩。

    wrong answer

    我们把集合 \(A\) 里的二元组 \((t,e)\) 看成对一个数组 \(a\) 执行 \(a_t\gets a_t+e\)

    我们把集合 \(B\) 里的二元组 \((t,e)\) 看成对一个数组 \(b\) 执行 \(b_t\gets b_t+e\)

    因为不强制在线,显然可以先把 \(t\) 离散化到 \(O(Q)\) 级别。

    那么题目实际上就是让我们取得数组 \(a\) 的一个前缀和 \(Sa\)\(b\) 的一个后缀和 \(Sb\),满足 \(a\) 最后一个数的下标小于等于 \(b\) 第一个数的下标,使得 \(\min(Sa,Sb)\) 尽可能大,在此前提下 \(mid\) 尽量大。

    容易想到这个东西关于 \(mid\) 是个凸函数,我们尝试去三分最终温度。

    可惜的是值域不是连续的,整数域上难以三分。

    模拟退火,我觉得它过不了 \(\textrm{2e6}\) 的询问。

    binary to ternary

    先不考虑 \(mid\) 的计算,只考虑 \(Ans\)

    考虑把三分变成二分,考虑到只有两个数组,发现 \(Ans\) 最大时一定满足这二者之一:

    • \(Sa\le Sb\),且 \(Sa\) 尽可能大。
    • \(Sa\ge Sb\),且 \(Sb\) 尽可能大。

    这两种情况是可以直接二分的。

    直接二分,还要维护前后缀和,复杂度 \(O(Q \log^2 Q)\)

    改为使用线段树二分,复杂度 \(O(Q \log Q)\)

    calculating \(mid\)

    线段树二分我们可以顺带找到对应取得最大值的位置 \(mid'\),但它不一定是最大的那个。

    对于 “\(Sa\le Sb\),且 \(Sa\) 尽可能大” 的情况,线段树二分的确可以直接满足 \(mid'=mid\),可以直接输出。

    但对于第二类 “\(Sa\ge Sb\),且 \(Sb\) 尽可能大” 的情况,我们则需要找到 \(\ge mid'\) 的第一个 \(i\) 使得 \(b_i\not = 0\)。此时 \(mid=i\)。 这可以直接使用一个 \(\textrm{multiset}\) 实现。

    Code

    细节不是很多,常数却有很大。

    \(\textrm{LOJ}\) 压时限过,在 \(\textrm{luogu}\) 怕是人没了。

    Author : Imakf
    #include <bits/stdc++.h>
    
    #define LL long long
    
    const int MX = 2e6 + 23;
    
    using namespace std;
    
    int read(){
    	char k = getchar(); int x = 0;
    	while(k < '0' || k > '9') k = getchar();
    	while(k >= '0' && k <= '9')
    		x = x * 10 + k - '0' ,k = getchar();
    	return x;
    }
    
    struct Operation{
    	int type ,t ,x ,y ,k ,id;
    	void readSign(){type = 1 ,t = read() ,x = read() ,y = read();}
    	void readWithdraw(){type = 2 ,k = read();}
    	bool operator <(const Operation &B)const{return x < B.x;}
    }O[MX];
    
    bool cmp(Operation A ,Operation B){return A.id < B.id;}
    
    struct node{
    	int l ,r ,s[2];
    	node *lch ,*rch;
    }*root;
    
    void pushup(node *x){
    	x->s[0] = x->lch->s[0] + x->rch->s[0];
    	x->s[1] = x->lch->s[1] + x->rch->s[1];
    }
    
    node *build(int l ,int r){
    	node *x = new node; x->l = l ,x->r = r ,x->s[0] = x->s[1] = 0;
    	if(l == r) x->lch = x->rch = nullptr;
    	else{int mid = (l + r) >> 1;
    		x->lch = build(l ,mid);
    		x->rch = build(mid + 1 ,r);
    		pushup(x);
    	}return x;
    }
    
    void add(node *x ,int p ,int belong ,int v){
    	if(x->l == x->r) return x->s[belong] += v ,void();
    	if(p <= x->lch->r) add(x->lch ,p ,belong ,v);
    	else               add(x->rch ,p ,belong ,v);
    	return pushup(x);
    }
    
    #define pi pair<int ,int>
    #define mp make_pair
    
    bool operator <(pi a ,pi b){
    	return a.first == b.first ? a.second < b.second : a.first < b.first;
    }
    pi max(pi a ,pi b){return a < b ? b : a;}
    
    pi qsmall(node *x ,int lsum = 0 ,int rsum = 0){ // ice <= fire 的最后一个位置
    	if(x->l == x->r) return mp(min(lsum + x->s[0] ,rsum + x->s[1]) ,x->l);
    	int nlsum = lsum + x->lch->s[0] ,nrsum = rsum + x->rch->s[1];
    	if(nlsum <= nrsum) return qsmall(x->rch ,nlsum ,rsum);
    	return qsmall(x->lch ,lsum ,nrsum);
    }
    
    pi qbig(node *x ,int lsum = 0 ,int rsum = 0){ // ice >= fire 的第一个位置
    	if(x->l == x->r) return mp(min(lsum + x->s[0] ,rsum + x->s[1]) ,x->l);
    	int nlsum = lsum + x->lch->s[0] ,nrsum = rsum + x->rch->s[1];
    	if(nlsum >= nrsum) return qbig(x->lch ,lsum ,nrsum);
    	return qbig(x->rch ,nlsum ,rsum);
    }
    
    int lsh[MX] ,lshcnt;
    
    int main(){
    	int Q = read();
    	for(int i = 1 ,op ; i <= Q ; ++i){
    		op = read();
    		if(op == 1) O[i].readSign();
    		else O[i].readWithdraw();
    		O[i].id = i;
    	}
    	sort(O + 1 ,O + 1 + Q);
    	int lastx = -1;
    	for(int i = 1 ; i <= Q ; ++i){
    		if(O[i].type == 2) continue;
    		if(O[i].x != lastx) lsh[++lshcnt] = O[i].x;
    		lastx = O[i].x ,O[i].x = lshcnt;
    	}
    	sort(O + 1 ,O + 1 + Q ,cmp);
    	root = build(1 ,lshcnt);
    
    	multiset<int> soldier;
    	soldier.insert(MX);
    
    	for(int i = 1 ; i <= Q ; ++i){
    		int p = i;
    		if(O[p].type == 1){
    			/*
    			printf("Add type = \"%s\" ,temperature = %d ,energy = %d\n" 
    					,O[p].t ? "fire" : "ice"
    					,O[p].x
    					,O[p].y);
    			*/
    			add(root ,O[p].x ,O[p].t ,O[p].y);
    			if(O[p].t) soldier.insert(O[p].x);
    		}
    		else{
    			p = O[p].k;
    			add(root ,O[p].x ,O[p].t ,-O[p].y);
    			if(O[p].t) soldier.erase(soldier.find(O[p].x));
    		}
    		
    		pi vbig = qbig(root) ,vsm = qsmall(root);
    		pi v = max(vbig ,vsm);
    		
    		// printf("Tmp pos = %d\n" ,lsh[v.second]);
    
    		if(!v.first){
    			puts("Peace");
    			continue;
    		}
    	
    		if(v.first == vsm.first && v.first != vbig.first){
    			printf("%d %d\n" ,lsh[v.second] ,v.first * 2);
    			continue;
    		}	
    		int pos = *soldier.lower_bound(v.second);
    		
    		if(v.first) printf("%d %d\n" ,lsh[pos] ,v.first * 2);
    	}
    	return 0;
    }
    
  • 相关阅读:
    SQLSERVER 数据连接字符串
    c#中 间隔两个小时执行一次
    移动开发者的自学宝典:十大在线编程学习网站
    C#j将DataTable转换成List
    C#将list转换为datatable
    SQL Server 高性能写入 摘抄
    解决控制面板删除不了的程序卸载
    卸载SQL 2008 R2 出现警告26003
    删除重复数据
    大数据量数据优化
  • 原文地址:https://www.cnblogs.com/imakf/p/13632146.html
Copyright © 2020-2023  润新知