[省选联考 2020 A/B 卷] 冰火战士

一、题目

点此看题

二、解法

其实这道题也不是特别难吧 \(......\) 但树状数组上二分是我第一次见。

我们把冰人和火人都按温度排序,那么考虑一个分界线 \(x\) ,问题就是求冰数组 \(x\) 对应的能量前缀和 \(\tt and\) 火数组 \(x\) 对应的能量后缀和最小值 的最大值,相同答案最大化 \(x\)

那么你把前缀和 \(/\) 后缀和看成关于 \(x\) 的函数,那么大概是这个样子的:

找到交点是不现实的,因为这个函数是不连续的。但是我们可以求出满足 \(y_1<y_2\) 的最大的 \(x\) ,那么这个 \(x\) 对应的答案是在取 \(y_1\) 当答案情况下最优的。如果我们再把 \(x\) 移动一点点(前提是要离散化温度),那么有 \(y_1\geq y_2\),这时候是取 \(y_2\) 当作答案的,但是注意我们还要找它对应的最大的 \(x\) 哦,然后取最大的输出即可。

直接二分实现上述过程的话是 \(O(n\log^2n)\) 的,恭喜你被卡成了 \(60\) 分。正解是树状数组上二分(线段树上二分常数太大了),你首先要知道树状数组的原理,节点 \(i\) 管辖的是 \((i-lowbit(i),i]\) 这一段区间。那么我们每次就用倍增的方式来二分,依次跳 \(2^{20},2^{19}......2^0\) ,那么根据树状数组的原理我们可以直接加上 \(a[now+2^?]\) 这个位置上树状数组的值,那么我们就维护了前缀和。

时间复杂度 \(O(n\log n)\) ,常数还很小。

\(\tt by\;the\;way\),我真的不知道为什么省选的时候我 \(60\) 分都可以写挂 \(......\)

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 2000005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,sum,op[M],t[M],x[M],y[M],p[M],b[2][M];
int lowbit(int x)
{
	return x&(-x);
}
void add(int x,int y,int op)
{
	for(int i=x;i<=n;i+=lowbit(i))
		b[op][i]+=y;
}
int ask(int x,int op)
{
	int res=0;
	for(int i=x;i>=1;i-=lowbit(i))
		res+=b[op][i];
	return res;
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		op[i]=read();t[i]=read();
		if(op[i]==1)
		{
			x[i]=read();y[i]=read();
			p[++m]=x[i];
		}
	}
	sort(p+1,p+1+m);
	m=unique(p+1,p+1+m)-p-1;
	for(int i=1;i<=n;i++)
	{
		if(op[i]==1)
		{
			x[i]=lower_bound(p+1,p+1+m,x[i])-p;
			if(t[i]==0) add(x[i],y[i],0);
			else add(x[i]+1,y[i],1),sum+=y[i];
		}
		else
		{
			if(t[t[i]]==0) add(x[t[i]],-y[t[i]],0);
			else add(x[t[i]]+1,-y[t[i]],1),sum-=y[t[i]];
		}
		int t1=0,f1=0,t2=0,f2=0,s0=0,s1=0;
		for(int i=20;i>=0;i--)
		{
			int to=t1+(1<<i);
			if(to>m) continue;//跳出去了
			if(s0+b[0][to]<sum-s1-b[1][to])
			{
				s0+=b[0][to];s1+=b[1][to];
				t1=to;
			}
		}
		f1=min(s0,sum-s1);
		if(t1<m)
		{
			f2=min(ask(t1+1,0),sum-ask(t1+1,1));//计算答案
			s0=s1=0;
			for(int i=20;i>=0;i--)//这里其实是找f2对应的点 
			{
				int to=t2+(1<<i),c1=s0+b[0][to],c2=sum-s1-b[1][to];
				if(to>m) continue;
				if(c1<c2)
				{
					s0+=b[0][to];s1+=b[1][to];
					t2=to;
				}
				else if(min(c1,c2)==f2)
				{
					s0+=b[0][to];s1+=b[1][to];
					t2=to;
				}
			}
		}
		if(f1==0 && f2==0) puts("Peace");
		else if(f1>f2) printf("%d %d\n",p[t1],2*f1);
		else printf("%d %d\n",p[t2],2*f2);
	}
}
posted @ 2020-12-26 16:25  C202044zxy  阅读(93)  评论(0编辑  收藏  举报