[省选联考 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);
}
}