【洛谷P6619】冰火战士
题目
题目链接:https://www.luogu.com.cn/problem/P6619
一场比赛即将开始。
每位战士有两个属性:温度和能量,有两派战士:冰系战士的技能会对周围造成降温冰冻伤害,因而要求场地温度不低于他的自身温度才能参赛;火系战士的技能会对周围造成升温灼烧伤害,因而要求场地温度不高于他的自身温度才能参赛。
当场地温度确定时,双方能够参赛的战士分别排成一队。冰系战士按自身温度从低到高排序,火系战士按自身温度从高到低排序,温度相同时能量大的战士排在前面。首先,双方的第一位战士之间展开战斗,两位战士消耗相同的能量,能量少的战士将耗尽能量退出比赛,而能量有剩余的战士将继续和对方的下一位战士战斗(能量都耗尽则双方下一位战士之间展开战斗)。如此循环,直至某方战士队列为空,比赛结束。
你需要寻找最佳场地温度:使冰火双方消耗总能量最高的温度的最高值。
现在,比赛还处于报名阶段,目前还没有任何战士报名,接下来你将不断地收到报名信息和撤回信息。其中,报名信息包含报名战士的派系和两个属性,撤回信息包含要撤回的报名信息的序号。每当报名情况发生变化(即收到一条信息)时,你需要立即报出当前局面下的最佳场地温度,以及该场地温度下双方消耗的总能量之和是多少。若当前局面下无论何种温度都无法开展比赛(某一方没有战士能参赛),则只要输出 Peace
。
思路
考场权值线段树被卡了 /kk。
首先题意就是要你维护两个序列 \(a\) 和 \(b\)。问你 \(k\) 取什么值时 \(a\) 中不大于 \(k\) 的元素和与 \(b\) 中不小于 \(k\) 的元素和之和最大。
显然可以二分 \(k\),然后取两个序列分别排序后的前缀与后缀和最接近时的 \(k\) 即可。注意当 \(k\) 所在的位置不是火法师的时候要一直往后移直到是火法师为止。
算法 1
可以考虑直接二分,然后利用树状数组维护两个序列的前缀和。后移的操作就直接暴力上 \(\operatorname{multiset}\) 即可。
时间复杂度 \(O(n\log^2 n)\),期望得分 \(60pts\)。
算法 2
发现上述二分 + 树状数组可以直接用权值线段树上二分代替。而后半部分我们可以根据求出的适合的温度 \(k\),设火法师中温度不小于 \(k\) 的有 \(t\) 个,那么就在权值线段树上找到最大的且后面有不少于 \(t\) 个火法师的温度 \(k'\)。那么 \(k'\) 即为答案。
可以把后缀和转化为前缀和求。上述所有操作均可以在 \(O(n \log n)\) 的时间复杂度内求出。
由于此方法常数极大,所以期望得分依然是 \(60pts\)。
算法 3
可以利用在树状数组上二分的黑科技来代替权值线段树(注意不等同与二分 + 树状数组)。时间复杂度 \(O(n\log n)\)。期望得分 \(100pts\)。
代码
注意以下代码在洛谷要开 \(\operatorname{-O2}\) 才可以过。但是看到 \(95\%\) 在洛谷过了的神仙都开了 \(\operatorname{-O2}\),所以姑且算在 CCF 少爷机上过了吧。
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=2000010,M=(1<<21);
int Q,tot,sum,cnt,b[N];
struct Query
{
int opt,t,x,y;
}ask[N];
inline int read()
{
int d=0; char ch=getchar();
while (!isdigit(ch)) ch=getchar();
while (isdigit(ch)) d=(d<<3)+(d<<1)+ch-48,ch=getchar();
return d;
}
struct BIT
{
int c[3][M+1];
inline void add(int x,int v,int id)
{
for (int i=x;i<=M;i+=i&-i)
c[id][i]+=v;
}
inline int query(int x,int id)
{
int ans=0;
for (int i=x;i;i-=i&-i)
ans+=c[id][i];
return ans;
}
}bit;
inline int binary1()
{
int l=1,r=M,mid,sum1=0,sum2=0;
while (l<r)
{
mid=(l+r)>>1;
if (sum1+bit.c[0][mid]<=sum-sum2-bit.c[1][mid])
{
sum1+=bit.c[0][mid]; sum2+=bit.c[1][mid];
l=mid+1;
}
else r=mid;
}
return l;
}
inline int binary2(int k)
{
int l=1,r=M,mid,sum=0;
while (l<r)
{
mid=(l+r)>>1;
if (sum+bit.c[2][mid]>=k) r=mid;
else l=mid+1,sum+=bit.c[2][mid];
}
return l;
}
int main()
{
Q=read();
for (int i=1;i<=Q;i++)
{
ask[i].opt=read(); ask[i].t=read();
if (ask[i].opt==1)
{
ask[i].x=read(); ask[i].y=read();
b[++tot]=ask[i].x;
}
}
sort(b+1,b+1+tot);
tot=unique(b+1,b+1+tot)-b-1;
for (int i=1;i<=Q;i++)
if (ask[i].opt==1)
ask[i].x=lower_bound(b+1,b+1+tot,ask[i].x)-b;
for (int i=1;i<=Q;i++)
{
if (ask[i].opt==1)
{
bit.add(ask[i].x,ask[i].y,ask[i].t);
if (ask[i].t)
{
sum+=ask[i].y; cnt++;
bit.add(ask[i].x,1,2);
}
}
else
{
int j=ask[i].t;
bit.add(ask[j].x,-ask[j].y,ask[j].t);
if (ask[j].t)
{
sum-=ask[j].y; cnt--;
bit.add(ask[j].x,-1,2);
}
}
int p=binary1(),q=bit.query(p-1,2),k=binary2(q+1);
int ans1=bit.query(k,0),ans2=sum-bit.query(k-1,1);
if (ans1 && ans2)
printf("%d %d\n",b[k],2*min(ans1,ans2));
else
printf("Peace\n");
}
return 0;
}