洛谷题目页面传送门
有两种战士:冰和火,每个战士都有自身温度和能量。冰战士能参战当且仅当自身温度不大于场地温度,火战士相反。对于一个确定的场地温度,双方所有能参战的战士互相消耗相等的能量直到某一方没有能量了。你需要支持(2)种(q)次操作:
- ( exttt1 x y z):一个战士报名,若(x=0)是冰战士否则是火战士,自身温度为(y),能量为(z)。(yinleft[1,2 imes10^9 ight]);
- ( exttt2 x):撤销第(x)次操作,保证第(x)次操作是报名操作。
每次操作后,你需要选择场地温度来最大化双方消耗的总能量,在最大化总能量的前提下最大化场地温度,输出这两个值。若无法消耗能量,输出( exttt{Peace})。
(qinleft[1,2 imes10^6 ight])。
设当前共有(n)个冰战士,第(i)个自身温度为(a_i),能量为(b_i);共有(m)个火战士,第(i)个自身温度为(c_i),能量为(d_i)。那么对于场地温度(e),冰战士的总能量为(sumlimits_{i=1}^n[a_ileq e]b_i),火战士的总能量为(sumlimits_{i=1}^m[c_igeq e]d_i),那么消耗的总能量显然是:
显然,冰战士总能量关于(e)单调递增,火战士总能量关于(e)单调递减,那么这两个函数的(min)就是非严格单峰的,这两个函数的差(冰减火)是单调递增的,在峰左边全负,在峰右边全正。
那么就很容易找峰了,只需要二分出最右一个差为负的位置(x)即可。于是可以基于场地温度(离散化后的,这个离散化之后一个点代表原来值域上朝右的一个区间,所以要把所有(y)和(y+1)压到离散化序列里)建一棵线段树,加/减战士时区间增加,找峰线段树二分,算答案时注意到峰左边(min)取冰,右边(min)取火,两边区间查最大值再取(max)即可。
以上是我在考场上的做法。很显然,这个做法常数非常大,又是线段树又是懒标记,操作次数又多,再加上(2 imes10^6)的数据范围,于是我被傻逼的CCF卡常成(60mathrm{pts})了(这里再吐槽一句,LOJ上是可以过的,CCF是甚么垃圾所谓的少爷机连洛谷都跑不过)。考虑优化。
一个显然的优化是,对于找到的峰(x),不用两边区间查最大值,因为最大答案只可能在(x)和(x+1)处。但是有个问题,这并不能保证右边能最大化场地温度,这样再线段树二分即可。
然后不难想到线段树转BIT。注意到BIT不能区间修改,于是可以转差分,区间修改就变成差分数组的双点修改,单点查值就是差分数组的前缀和。那么如何在BIT上1log二分呢?注意到,线段树上的二分并不需要单调性,而这里需要二分于上的火函数和差函数都是单调的,相当于是个多余条件?暗示了我们可以转到BIT上。
这是一个很巧妙的trick:BIT倍增。解决的是这样的问题:求最右边的前缀和【某个关系】某个常数的那一处,其中前缀和数组与【某个关系】同方向单调(恰与本题吻合)。考虑二分转倍增,从左往右逼近(不能从右往左,那样就要反过来建,在这题上恰合适),从大到小枚举(2)的指数,可以发现从当前位置到当前位置加上当前枚举到的(2)的幂的位置中间那一段的和恰好是被记录的,因为(2)的指数是从大到小枚举的,所以中间那一段的长度一定是右端点的(mathrm{lowbit})。然后就可以正常倍增了。over。
现在加个快读快写开个洛谷自带O2即可AC。
代码:
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define X first
#define Y second
char _buf[10000000],*_st=_buf,*_ed=_buf;
#define getchar() (_st==_ed&&(_ed=(_st=_buf)+fread(_buf,1,1<<22,stdin),_st==_ed)?EOF:*_st++)
void read(int &x){//快读
x=0;char c=getchar();
while(!isdigit(c))c=getchar();
while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
void prt(int x){//快写
if(x>=10)prt(x/10);
putchar(x%10^48);
}
const int lowbit(int x){return x&-x;}
const int QU=2000000;
int qu;
struct query{int tp,x,y,z;}qry[QU+1];
vector<int> nums;
void discrete(){//离散化
sort(nums.begin(),nums.end());
nums.resize(unique(nums.begin(),nums.end())-nums.begin());
for(int i=1;i<=qu;i++)qry[i].y=lower_bound(nums.begin(),nums.end(),qry[i].y)-nums.begin()+1;
}
struct bitree{//BIT
int sum0[2*QU+1]/*冰*/,sum1[2*QU+1]/*火*/;
void init(){
memset(sum0,0,sizeof(sum0));memset(sum1,0,sizeof(sum1));
}
void add0(int x,int v){//加冰
while(x<=nums.size())sum0[x]+=v,x+=lowbit(x);
}
void add1(int x,int v){//加火
while(x<=nums.size())sum1[x]+=v,x+=lowbit(x);
}
int lasne(){//最右的差为负的一处
int ans=0;int now=0;
for(int i=22;~i;i--)if(ans+(1<<i)<=nums.size()&&now+sum0[ans+(1<<i)]-sum1[ans+(1<<i)]<0)ans+=1<<i,now+=sum0[ans]-sum1[ans];//倍增
return ans;
}
int laseq(int v){//最右的火函数等于v的一处,同上
int ans=0;int now=0;
for(int i=22;~i;i--)if(ans+(1<<i)<=nums.size()&&now+sum1[ans+(1<<i)]>=v)ans+=1<<i,now+=sum1[ans];
return ans;
}
int Sum0(int x){//冰,单点查值->前缀和
int res=0;
while(x)res+=sum0[x],x-=lowbit(x);
return res;
}
int Sum1(int x){//火,同上
int res=0;
while(x)res+=sum1[x],x-=lowbit(x);
return res;
}
}bit;
int main(){
read(qu);
for(int i=1;i<=qu;i++){
read(qry[i].tp);read(qry[i].x);
if(qry[i].tp==1)read(qry[i].y),read(qry[i].z);
else qry[i].y=qry[qry[i].x].y,qry[i].z=qry[qry[i].x].z,qry[i].x=qry[qry[i].x].x;
nums.pb(qry[i].y);nums.pb(qry[i].y+1);//y和y+1都要pb进去
}
discrete();
for(int i=1;i<=qu;i++){
int tp=qry[i].tp,x=qry[i].x,y=qry[i].y,z=qry[i].z;
if(x==0)bit.add0(y,tp==1?z:-z);//差分
else bit.add1(1,tp==1?z:-z),bit.add1(y+1,tp==1?-z:z);//差分
int lasne=bit.lasne(),ans1=0,ans2=0;
ans1=bit.Sum0(lasne);//找峰
if(lasne+1<=nums.size())ans2=bit.Sum1(lasne+1);//右边也要考虑一下
if(!ans1&&!ans2)puts("Peace");
else if(ans2>=ans1)prt(nums[bit.laseq(ans2)]-1/*右边的最右处*/),putchar(' '),prt(ans2<<1),putchar('
');
else prt(nums[lasne]-1),putchar(' '),prt(ans1<<1),putchar('
');
}
return 0;
}