题意
两个人玩取石子游戏,一开始小D有XXX颗石子,小Y有YYY颗石子。两人轮流取石子,小D先取,共NNN轮,第iii轮当前操作者会从对方那里收取AiA_iAi颗石子,若不足AiA_iAi颗则全取来。问最后小D手里有多少石子。
有QQQ次修改,每次可以修改XXX或YYY或某个AiA_iAi。
1≤N,Q≤5∗1051\leq N,Q \leq5*10^51≤N,Q≤5∗105。
题解
可以理解为初始值为x=Xx=Xx=X,总和为S=X+YS=X+YS=X+Y,每次操作即为x=max(0,min(x+Bi,S))x=max(0,min(x+B_i,S))x=max(0,min(x+Bi,S))。
显然答案随初始值增加单调不降。
对于单次询问,考虑维护前缀最大值maxmaxmax,最小值minminmin,前缀和sumsumsum。
若max−min≤summax-min\leq summax−min≤sum,令y=max(−min,max(x,sum−max))y=max(-min,max(x,sum-max))y=max(−min,max(x,sum−max)),最终答案即为y+sumy+sumy+sum。
否则答案与初值无关。
考虑用线段树维护,每次计算右儿子的max−minmax-minmax−min,若≤sum\leq sum≤sum,可以先递归计算出左儿子的答案,此时可以O(1)O(1)O(1)得出右儿子的答案,否则答案与左儿子的答案无关,直接随意设一个数作为左儿子的答案传入右儿子递归计算即可。
复杂度O((N+Q)logN)O((N+Q)logN)O((N+Q)logN)。
#include <bits/stdc++.h>
#define FR first
#define SE second
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pr;
int fir[500005];
namespace SGT {
ll sumv[2000000],minn[2000000],maxn[2000000];
inline void pushup(int o) {
sumv[o]=sumv[o*2]+sumv[o*2+1];
minn[o]=min(minn[o*2],sumv[o*2]+minn[o*2+1]);
maxn[o]=max(maxn[o*2],sumv[o*2]+maxn[o*2+1]);
}
void build(int l,int r,int o) {
if (l==r) sumv[o]=minn[o]=maxn[o]=fir[l];
else {
int m=((l+r)>>1);
build(l,m,o*2);
build(m+1,r,o*2+1);
pushup(o);
}
}
void update(int l,int r,int o,int p,int q) {
if (l==r) sumv[o]=minn[o]=maxn[o]=q;
else {
int m=((l+r)>>1);
if (m>=p) update(l,m,o*2,p,q);
else update(m+1,r,o*2+1,p,q);
pushup(o);
}
}
ll query(int l,int r,int o,ll p,ll q) {
if (maxn[o]-minn[o]<q) {
ll l=-minn[o],r=q-maxn[o];
p=max(p,l);
p=min(p,r);
return p+sumv[o];
}
int m=((l+r)>>1);
if (maxn[o*2+1]-minn[o*2+1]>=q) return query(m+1,r,o*2+1,0,q);
else {
ll t=query(l,m,o*2,p,q);
return query(m+1,r,o*2+1,t,q);
}
}
}
int main() {
int n,m;
ll sx,sy;
scanf("%d%d%lld%lld",&n,&m,&sx,&sy);
for(int i=1;i<=n;i++) {
scanf("%d",&fir[i]);
if (!(i&1)) fir[i]=-fir[i];
}
SGT::build(0,n,1);
for(int i=1;i<=m;i++) {
int kind;
scanf("%d",&kind);
if (kind==1) scanf("%lld",&sx);
else if (kind==2) scanf("%lld",&sy);
else {
int x,y;
scanf("%d%d",&x,&y);
if (!(x&1)) y=-y;
SGT::update(0,n,1,x,y);
}
printf("%lld\n",SGT::query(0,n,1,sx,sx+sy));
}
return 0;
}
附记
今天模拟赛对着这题自闭了4h,想到了除了线段树二分外的其他部分。但是对于max−min>summax-min>summax−min>sum的情况不会,一直觉得这种情况做法是维护关于sumsumsum的分段函数,结果发现并不能维护,瞎蒙了个结论还错了。
感觉题解十分巧妙,区分了我这种思维不行的选手,以后可能需要加强思维锻炼。省选快到了,感觉状态还没调整好,要及时调整。