XSY #3478 取石子

本文讨论了一种两人取石子游戏的策略,其中小D和小Y轮流取石子,每轮可以收取对方一定数量的石子。通过分析,发现答案随着初始石子数增加而单调不降。利用线段树维护前缀最大值、最小值和前缀和,以O((N+Q)logN)的时间复杂度解决此问题。在遇到特定情况时,需要巧妙地处理计算逻辑。文章作者反思了自己的思维锻炼,并表达了对即将到来的竞赛的担忧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

两个人玩取石子游戏,一开始小D有XXX颗石子,小Y有YYY颗石子。两人轮流取石子,小D先取,共NNN轮,第iii轮当前操作者会从对方那里收取AiA_iAi颗石子,若不足AiA_iAi颗则全取来。问最后小D手里有多少石子。
QQQ次修改,每次可以修改XXXYYY或某个AiA_iAi
1≤N,Q≤5∗1051\leq N,Q \leq5*10^51N,Q5105

题解

可以理解为初始值为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 summaxminsum,令y=max(−min,max(x,sum−max))y=max(-min,max(x,sum-max))y=max(min,max(x,summax)),最终答案即为y+sumy+sumy+sum
否则答案与初值无关。
考虑用线段树维护,每次计算右儿子的max−minmax-minmaxmin,若≤sum\leq sumsum,可以先递归计算出左儿子的答案,此时可以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&gt;summax-min&gt;summaxmin>sum的情况不会,一直觉得这种情况做法是维护关于sumsumsum的分段函数,结果发现并不能维护,瞎蒙了个结论还错了。
感觉题解十分巧妙,区分了我这种思维不行的选手,以后可能需要加强思维锻炼。省选快到了,感觉状态还没调整好,要及时调整。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值