XSY #3478 取石子

题意

两个人玩取石子游戏,一开始小D有 X X 颗石子,小Y有 Y Y 颗石子。两人轮流取石子,小D先取,共 N N 轮,第 i i 轮当前操作者会从对方那里收取 A i A_i 颗石子,若不足 A i A_i 颗则全取来。问最后小D手里有多少石子。
Q Q 次修改,每次可以修改 X X Y Y 或某个 A i A_i
1 N , Q 5 1 0 5 1\leq N,Q \leq5*10^5

题解

可以理解为初始值为 x = X x=X ,总和为 S = X + Y S=X+Y ,每次操作即为 x = m a x ( 0 , m i n ( x + B i , S ) ) x=max(0,min(x+B_i,S))
显然答案随初始值增加单调不降。
对于单次询问,考虑维护前缀最大值 m a x max ,最小值 m i n min ,前缀和 s u m sum
m a x m i n s u m max-min\leq sum ,令 y = m a x ( m i n , m a x ( x , s u m m a x ) ) y=max(-min,max(x,sum-max)) ,最终答案即为 y + s u m y+sum
否则答案与初值无关。
考虑用线段树维护,每次计算右儿子的 m a x m i n max-min ,若 s u m \leq sum ,可以先递归计算出左儿子的答案,此时可以 O ( 1 ) O(1) 得出右儿子的答案,否则答案与左儿子的答案无关,直接随意设一个数作为左儿子的答案传入右儿子递归计算即可。
复杂度 O ( ( N + Q ) l o g N ) 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,想到了除了线段树二分外的其他部分。但是对于 m a x m i n &gt; s u m max-min&gt;sum 的情况不会,一直觉得这种情况做法是维护关于 s u m sum 的分段函数,结果发现并不能维护,瞎蒙了个结论还错了。
感觉题解十分巧妙,区分了我这种思维不行的选手,以后可能需要加强思维锻炼。省选快到了,感觉状态还没调整好,要及时调整。

猜你喜欢

转载自blog.csdn.net/qq_38609262/article/details/89313174