ZSTU 4241 圣杯战争(线段树+经典)
题意:CS召唤了n个实验怪兽,第i号怪兽在i这个位置出。并把KI召唤出的第i位从者安排在pos(i)处,总共有m位从者。
第i只怪兽有战斗力atk(i), 而i号从者的体力为AP(i)。如果从者想要移动,他必须战胜他当前位置处的怪兽,战胜的条件为AP>=atk, 然后该从者的AP会减少atk, 注意从者只能从i移动到i+1,或移动到i-1处,注意一旦开始移动就只能向一个方向移动了。
CS会给出Q次询问,每次给出一个区间[l, r]: 要求KI只能派出一个从者,并能打败这个区间中尽可能多的怪物,求能打败的最大怪物数,强制在线
请求出只派出一位从者的情况下在[pl,pr]这个区间中所能战胜的最大怪物数
题解:开始就有个比较好的想法,但是总有一个小问题无法解决,然后突然之间想通了!!!
首先题目不要求修改,所以我们预处理每位从者可以到达的左与右的最远位置。从者只能一路向左或向右而atk>=0,因此根据前缀和的单调性二分找到左与右的最远的位置(我用的是map,注意设置哨兵减少边界判断),接着左右各扫一遍找到每个点向左向右最远扩展的距离
然后问题转化为:给一个区间,问此区间内可每个点可扩展距离的最大值是多少(注意从者可能不在这个区间内,但是移动进这个区间),但是这儿有一个问题就是:需要保证每个点向两边扩展不得超过给定的区间范围,而超过的部分是无效的,因此不能直接使用线段树维护区间最值
但是这个有个隐藏的条件就是(向右的情况几乎一样):每个点向左可扩展的距离一定是大于等于他右边最近的一个减一,所以当位置3可扩展距离为3时,向左超过了区间[2,10]后,则2这个位置也一定会超过,因此我们只需要3之后的就好了,这样在给定区间中,就找到最右边一个向左扩展刚好大于等于左边界的那个位置,接着他左边的值就可以不计算,右边的值利用线段树找区间最大值,他这个位置直接找到左边界的值(注意没有这个位置的情况)。这个位置我们需要将求出向左扩展的最大距离从左到右的位置依次减去1,2,3...,再利用线段树维护(经典),因为向左扩展时从左到右每个值比前一个值到左边界的距离要大一个,因此我们这样减去后,就直接求区间内最左边一个大于等于
(-pl+1)的位置
#include<set> #include<map> #include<queue> #include<stack> #include<cmath> #include<vector> #include<string> #include<cstdio> #include<cstring> #include<iomanip> #include<stdlib.h> #include<iostream> #include<algorithm> using namespace std; #define eps 1E-8 /*注意可能会有输出-0.000*/ #define Sgn(x) (x<-eps? -1 :x<eps? 0:1)//x为两个浮点数差的比较,注意返回整型 #define Cvs(x) (x > 0.0 ? x+eps : x-eps)//浮点数转化 #define zero(x) (((x)>0?(x):-(x))<eps)//判断是否等于0 #define mul(a,b) (a<<b) #define dir(a,b) (a>>b) typedef long long ll; typedef unsigned long long ull; const int Inf=1<<28; const ll INF=1ll<<60; const double Pi=acos(-1.0); const int Mod=1e9+7; const int Max=200010<<2; int Segtrl[Max],Segtrr[Max],maxl[Max],maxr[Max];//两颗线段树 int lef[Max],rig[Max];//每个点向左向右可以到的最大距离 int atk[Max],pos[Max],ap[Max]; ll dig1[Max],dig2[Max];//左右前缀和 int n; map<ll,int> suml,sumr; void Solve(int m)//处理lef与rig { memset(lef,0,sizeof(lef)); memset(rig,0,sizeof(rig)); for(int i=1; i<=m; ++i) //找到每个人可以到的左右最大距离 { int posx=pos[i]; ll now=dig2[posx+1]+ap[i]; lef[posx]=max(lef[posx],posx-(suml.upper_bound(now)->second));//找到大于now的最近的位置 //printf("%lld\n",now); now=dig1[posx-1]+ap[i]; rig[posx]=max(rig[posx],(sumr.upper_bound(now)->second)-posx); //printf("OK%d %d %d\n",posx,lef[posx],rig[posx]); } for(int i=1; i<=n; ++i) //枚举n找到每个位置可以到的最大距离 { rig[i]=max(rig[i],rig[i-1]-1); } for(int i=n; i>0; --i) { lef[i]=max(lef[i],lef[i+1]-1); } // for(int i=1;i<=n;++i) // printf("%d %d\n",lef[i],rig[i]); return; } void Upnow(int now,int next) { Segtrl[now]=max(Segtrl[next],Segtrl[next|1]); Segtrr[now]=max(Segtrr[next],Segtrr[next|1]); maxl[now]=max(maxl[next],maxl[next|1]); maxr[now]=max(maxr[next],maxr[next|1]); return ; } void Create(int sta,int enn,int now) { if(sta==enn) { Segtrl[now]=lef[sta]; Segtrr[now]=rig[sta]; maxl[now]=lef[sta]-sta;//关键 maxr[now]=rig[sta]-(n-sta+1);//关键 return; } int next=(now<<1); int mid=(sta+enn>>1); Create(sta,mid,next); Create(mid+1,enn,next|1); Upnow(now,next); return; } int flag; int Update(int sta,int enn,int now,int x,int y,int z,int hh) { if(sta==enn) { flag=1; return sta; } int res; int next=(now<<1); int mid=(sta+enn>>1); if(!hh)//最左边 { res=n+1; if(!flag&&mid>=x&&maxr[next]>=z) res=min(res,Update(sta,mid,next,x,y,z,hh)); if(!flag&&mid<y&&maxr[next|1]>=z) res=min(res,Update(mid+1,enn,next|1,x,y,z,hh)); } else { res=0; if(!flag&&mid<y&&maxl[next|1]>=z) res=max(res,Update(mid+1,enn,next|1,x,y,z,hh)); if(!flag&&mid>=x&&maxl[next]>=z) res=max(res,Update(sta,mid,next,x,y,z,hh)); } return res; } int Query(int sta,int enn,int now,int x,int y,int hh) { if(sta>=x&&enn<=y) { //printf("OK%d %d\n",sta,enn); if(!hh) return Segtrr[now]; return Segtrl[now]; } int next=(now<<1); int mid=(sta+enn>>1); int res=0; if(mid>=x) res=max(res,Query(sta,mid,next,x,y,hh)); if(mid<y) res=max(res,Query(mid+1,enn,next|1,x,y,hh)); return res; } int main() { int t,m,q; scanf("%d",&t); while(t--) { suml.clear(); sumr.clear(); scanf("%d %d %d",&n,&m,&q); dig1[0]=0ll; for(int i=1; i<=n; ++i) { scanf("%d",&atk[i]); dig1[i]=dig1[i-1]+atk[i];//前缀和 if(!sumr.count(dig1[i]))//仔细想想 sumr[dig1[i]]=i;//放入map } dig2[n+1]=0ll; for(int i=n; i>0; --i) { dig2[i]=dig2[i+1]+atk[i]; if(!suml.count(dig2[i])) suml[dig2[i]]=i; } dig1[n+1]=INF; dig2[0]=INF; sumr[0ll]=0; suml[0ll]=n+1; suml[INF]=0; sumr[INF]=n+1;//以上都为添加两个哨兵 for(int i=1; i<=m; ++i) { scanf("%d",&pos[i]); } for(int i=1; i<=m; ++i) { scanf("%d",&ap[i]); } Solve(m); Create(1,n,1); int tmp=0,manx; int pl,pr; while(q--) { manx=0; scanf("%d %d",&pl,&pr); pl^=tmp; pr^=tmp; if(pl>pr) swap(pl,pr); pl=max(pl,1); pr=min(pr,n); //printf("%d %d\n",pl,pr); flag=0; int posl=Update(1,n,1,pl,pr,pr-n,0);//找最左边的位置(向右扩展的) if(posl>pr) manx=max(manx,Query(1,n,1,pl,pr,0));//printf("%d %d\n",pl,posl); else if(posl>pl) manx=max(manx,max(Query(1,n,1,pl,posl-1,0),pr-posl+1));//posl这个位置一定是可以扩展到pr的 else manx=max(manx,pr-posl+1); flag=0; int posr=Update(1,n,1,pl,pr,-pl+1,1);//printf("%d %d\n",posr,pr); if(posr<pl) manx=max(manx,Query(1,n,1,pl,pr,1)); else if(posr<pr) manx=max(manx,max(Query(1,n,1,posr+1,pr,1),posr-pl+1)); else manx=max(manx,posr-pl+1); printf("%d\n",manx); tmp=manx; } } return 0; }