loj3299.「联合省选 2020 A | B」冰火战士
90% 的数据是来搞笑吗,真的有人会正解还不会离散化吗。
感觉是非常好的一题,以前树状数组只是背板子,做完这一题后算是真正理解了树状数组。
显然冰系出战的范围是一个前缀,火系出战的是一个后缀,这两个函数一个单调不增,一个单调不降,答案就是两个函数的 \(\min\) 在整点处取到的最大值的两倍。
答案只有可能在两个函数交点的前后取到,一个自然的想法是直接二分出这个极值,配合线段树或树状数组容易做到 \(O(Q\log^2Q)\),可以得到 \(60\) 分的好成绩。
考虑怎样砍掉一只 \(\log\)。不难想到,线段树+二分=线段树二分,而这样就少了一个 \(\log\)。进行两次线段树二分,第一次二分出两函数相交前的最后一个位置,然后下一个位置就是两函数相交后的第一个位置,这样一次二分+两次线段树区间查询得到了两边的最小值的最大值。比较一下,如果是相交前的值更大,就直接输出,否则可能会出现相交后连续一段答案全部相同的情况,而题目要求出温度最大值,于是再进行一次线段树二分二分出来这一段的右端点就是答案。这样时间复杂度降为 \(O(Q\log Q)\),可以通过此题……吗?
很不幸,线段树二分常数太大了,还是过不去。
这样一来我们不能使用线段树,那么只能使用树状数组了,查询很好解决,冰系直接查前缀,火系总和减去前缀即可。然后是一个重要的 trick,我们可以在树状数组上用倍增的方式来实现二分的操作。因为树状数组上编号为 \(i\) 的节点保存的是 \((i-lowbit(i),i]\) 的信息,这样我们从高位向低位枚举,类似求 LCA 的方法,如果满足要求就跳,这样就实现了和二分一样的效果。
但是……每次倍增跳到一个新的节点不是需要查询吗?这样还是 \(O(Q\log^2 Q)\) 的?
于是考虑怎样在倍增的时候 \(O(1)\) 在树状数组上查询前缀和。朴素的查询是单次 \(O(\log Q)\) 的且无法优化,这启示我们必须利用前面倍增得到的信息。还是回到节点 \(i\) 保存的信息的范围是 \((i-lowbit(i),i]\) 上面,我们发现由于每次增加的一定都是当前数位最低的一个 \(1\),换句话说,如果当前跳到的节点是 \(p\),上一个节点是 \(pos\),那么一定有 \(p=pos+lowbit(p)\),前面 \([1,pos]\) 的前缀和已经求出了,后面 \((pos,p]\) 的信息怎么求?对前面的式子移项,得到 \(pos=p-lowbit(p)\) 所以这个区间就是 \((p-lowbit(p),p]\),也就是树状数组上编号为 \(p\) 的节点的值。这样我们就实现了倍增的时候 \(O(1)\) 查询前缀和。
于是树状数组上倍增模拟线段树二分的全部问题都已经解决,直接和线段树二分时进行两次意义完全相同的倍增即可。
时间复杂度依然是 \(O(Q\log Q)\),但树状数组常数就小得多了,可以通过。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,ans[2000001][2],node[2000001],cnt,sum;
struct element
{
int opt,t,x,y;
}a[2000001];
inline int read()
{
int x=0;
char c=getchar();
while(c<'0'||c>'9')
c=getchar();
while(c>='0'&&c<='9')
{
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x;
}
void print(int x)
{
if(x>=10)
print(x/10);
putchar(x%10+'0');
}
inline int lowbit(int x)
{
return x&-x;
}
inline void update(int x,int val,int tag)
{
for(;x<=cnt;x+=lowbit(x))
ans[x][tag]+=val;
}
inline int query(int x,int tag)
{
int res=0;
for(;x;x-=lowbit(x))
res+=ans[x][tag];
return res;
}
int main()
{
n=read();
for(register int i=1;i<=n;++i)
{
a[i].opt=read(),a[i].t=read();
if(a[i].opt==1)
{
a[i].x=read(),a[i].y=read();
node[++cnt]=a[i].x;
}
}
sort(node+1,node+cnt+1);
cnt=unique(node+1,node+cnt+1)-node-1;
for(register int i=1;i<=n;++i)
{
if(a[i].opt==1)
{
a[i].x=lower_bound(node+1,node+cnt+1,a[i].x)-node;
if(!a[i].t)
update(a[i].x,a[i].y,0);
else
{
sum+=a[i].y;
update(a[i].x+1,a[i].y,1);
}
}
else
if(!a[a[i].t].t)
update(a[a[i].t].x,-a[a[i].t].y,0);
else
{
sum-=a[a[i].t].y;
update(a[a[i].t].x+1,-a[a[i].t].y,1);
}
int pos=0,ans1=0,ans2=0,tmp1=0,tmp2=sum;
for(register int j=20;~j;--j)
{
if((pos|(1<<j))>cnt)
continue;
int p=pos|(1<<j);
tmp1+=ans[p][0];
tmp2-=ans[p][1];
if(tmp1>=tmp2)
{
tmp1-=ans[p][0];
tmp2+=ans[p][1];
continue;
}
ans1=tmp1;
pos=p;
}
if(ans1&&pos==cnt)
{
print(node[pos]);
putchar(' ');
print(ans1<<1);
putchar('\n');
continue;
}
ans2=sum-query(pos+1,1);
if((!ans1&&!ans2)||query(pos+1,0)<ans2)
{
puts("Peace");
continue;
}
if(ans1>ans2)
{
print(node[pos]);
putchar(' ');
print(ans1<<1);
putchar('\n');
continue;
}
tmp1=0,tmp2=sum,pos=0;
for(register int j=20;~j;--j)
{
if((pos|(1<<j))>cnt)
continue;
int p=pos|(1<<j);
tmp1+=ans[p][0];
tmp2-=ans[p][1];
if(tmp1<tmp2||tmp2==ans2)
{
pos=p;
continue;
}
tmp1-=ans[p][0];
tmp2+=ans[p][1];
}
print(node[pos]);
putchar(' ');
print(ans2<<1);
putchar('\n');
}
return 0;
}