题意
两个人玩取石子游戏,一开始小D有
颗石子,小Y有
颗石子。两人轮流取石子,小D先取,共
轮,第
轮当前操作者会从对方那里收取
颗石子,若不足
颗则全取来。问最后小D手里有多少石子。
有
次修改,每次可以修改
或
或某个
。
。
题解
可以理解为初始值为
,总和为
,每次操作即为
。
显然答案随初始值增加单调不降。
对于单次询问,考虑维护前缀最大值
,最小值
,前缀和
。
若
,令
,最终答案即为
。
否则答案与初值无关。
考虑用线段树维护,每次计算右儿子的
,若
,可以先递归计算出左儿子的答案,此时可以
得出右儿子的答案,否则答案与左儿子的答案无关,直接随意设一个数作为左儿子的答案传入右儿子递归计算即可。
复杂度
。
#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,想到了除了线段树二分外的其他部分。但是对于
的情况不会,一直觉得这种情况做法是维护关于
的分段函数,结果发现并不能维护,瞎蒙了个结论还错了。
感觉题解十分巧妙,区分了我这种思维不行的选手,以后可能需要加强思维锻炼。省选快到了,感觉状态还没调整好,要及时调整。