【XSY3478】取石子(经典问题)

题面

取石子

题解

只考虑一方,每一个操作都可以写成 x ← max ⁡ ( 0 , min ⁡ ( S , x + B i ) ) x\gets \max(0,\min(S,x+B_i)) xmax(0,min(S,x+Bi)) 的形式。

法一:

定义 “碰壁” 表示当前 x ← max ⁡ ( 0 , min ⁡ ( S , x + B i ) ) x\gets \max(0,\min(S,x+B_i)) xmax(0,min(S,x+Bi)) 操作中对 0 0 0 max ⁡ \max max 和对 S S S min ⁡ \min min 之一起了作用(即 x + B i x+B_i x+Bi 超出了 [ 0 , S ] [0,S] [0,S] 范围)。

在所有操作前插入一个 − ∞ -\infty 操作和一个 X X X 操作,这样做的好处是:游戏一定是从 “先碰了一次下壁” 开始的。

考虑找出最后一次碰壁的时间 e d ed ed 以及它碰的是哪个壁,找出来之后就好做了。

我们将整个游戏的情形用下图的示意图表示:

在这里插入图片描述

上下两条黑线分别表示上下壁,红线是 x x x 随操作的变化形成的折线。也就是说,我们这么看游戏中的碰壁:先碰若干次下壁、再碰若干次上壁、再碰若干次下壁……。

我们把连续碰某一侧壁的部分称为 “一段”,一段中最后一次碰壁的位置称为这段的 “结尾”。我们发现,相邻两段的结尾之间一定存在两个点 i , j i,j i,j 使得 ∣ ∑ k = i j B k ∣ > S \left|\sum\limits_{k=i}^jB_k\right|>S k=ijBk>S。同时若存在 i , j i,j i,j 满足 ∣ ∑ k = i j B k ∣ > S \left|\sum\limits_{k=i}^jB_k\right|>S k=ijBk>S 那么 i i i 或之后一定会有碰壁。

这有助于我们定位最后一段的位置:找到 i i i 最大的 i , j i,j i,j 满足 ∣ ∑ k = i j B k ∣ > S \left|\sum\limits_{k=i}^jB_k\right|>S k=ijBk>S,那么最后一段一定在 i i i 之后而且 i i i 之后不存在其他段。

定位了最后一段之后怎么定位最后一次碰壁呢?由于 i i i 之后只会在同一侧碰壁,所以我们找到 i i i 之后 ∣ ∑ k = i e d B k ∣ \left|\sum\limits_{k=i}^{ed}B_k\right| k=iedBk 最大的位置即为 e d ed ed。而且我们能通过 ∣ ∑ k = i e d B k ∣ \left|\sum\limits_{k=i}^{ed}B_k\right| k=iedBk 的正负来判断碰的是哪一侧壁。

法二:

考虑分治,假设我们当前要解决 solve ⁡ ( x , l , r ) \operatorname{solve}(x,l,r) solve(x,l,r):进来的数是 x x x,经过操作 B l , ⋯   , B r B_l,\cdots,B_r Bl,,Br 后得到的是什么。

分两类讨论:

  • [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 中存在 i , j i,j i,j 满足 ∣ ∑ k = i j B k ∣ > S \left|\sum\limits_{k=i}^jB_k\right|>S k=ijBk>S,那么在 [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 中一定会碰壁,直接那么 [ l , m i d ] [l,mid] [l,mid] 操作就相当于没用了,直接进入 solve ⁡ ( ∗ , m i d + 1 , r ) \operatorname{solve}(*,mid+1,r) solve(,mid+1,r) 解决即可,其中 ∗ * 填什么并不重要。
  • [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 中不存在 i , j i,j i,j 满足 ∣ ∑ k = i j B k ∣ > S \left|\sum\limits_{k=i}^jB_k\right|>S k=ijBk>S,此时并不代表着在 [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 中一定不会碰壁,但可以说明在 [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 中至多只会碰同一侧的壁,这跟上一题最后一部分是一样的。那么我们可以先求出 x x x 经过 [ l , m i d ] [l,mid] [l,mid] 会得到的数 x ′ = solve ⁡ ( x , l , m i d ) x'=\operatorname{solve}(x,l,mid) x=solve(x,l,mid),然后找到 [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 中满足 ∣ ∑ k = m i d + 1 j B k ∣ \left|\sum\limits_{k=mid+1}^jB_k\right| k=mid+1jBk 最大的位置 j j j,那么 j j j 就是 [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 中最后一次碰壁的位置(当然需要先根据 x ′ + ∑ k = m i d + 1 j B k x'+\sum\limits_{k=mid+1}^jB_k x+k=mid+1jBk 判断有没有碰壁)。

法二代码:

#include<bits/stdc++.h>

#define N 500010
#define ll long long

using namespace std;

inline int read()
{
    
    
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
    
    
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
    
    
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

int n,q,X,Y,S;
ll sum[N<<2],maxn[N<<2],minn[N<<2];

void up(int k)
{
    
    
	sum[k]=sum[k<<1]+sum[k<<1|1];
	maxn[k]=max(maxn[k<<1],sum[k<<1]+maxn[k<<1|1]);
	minn[k]=min(minn[k<<1],sum[k<<1]+minn[k<<1|1]);
}

void build(int k,int l,int r)
{
    
    
	if(l==r)
	{
    
    
		int a=read();
		if(!(l&1)) a=-a;
		sum[k]=a,maxn[k]=max(0,a),minn[k]=min(0,a);
		return;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	up(k);
}

void update(int k,int l,int r,int x,int a)
{
    
    
	if(l==r)
	{
    
    
		if(!(l&1)) a=-a;
		sum[k]=a,maxn[k]=max(0,a),minn[k]=min(0,a);
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) update(k<<1,l,mid,x,a);
	else update(k<<1|1,mid+1,r,x,a);
	up(k);
}

int solve(int x,int k,int l,int r)
{
    
    
	if(l==r) return (int)max(0ll,min((ll)S,x+sum[k]));
	int mid=(l+r)>>1,rc=k<<1|1;
	if(maxn[rc]-minn[rc]>S) return solve(0,k<<1|1,mid+1,r);
	x=solve(x,k<<1,l,mid);
	if(x+maxn[rc]>S) return S+sum[rc]-maxn[rc];
	if(x+minn[rc]<0) return sum[rc]-minn[rc];
	return x+sum[rc];
}

int main()
{
    
    
//	freopen("stone3.in","r",stdin);
//	freopen("stone3_my.out","w",stdout);
	n=read(),q=read(),X=read(),Y=read(),S=X+Y;
	build(1,1,n);
	while(q--)
	{
    
    
		int opt=read();
		if(opt==1) X=read(),S=X+Y;
		if(opt==2) Y=read(),S=X+Y;
		if(opt==3)
		{
    
    
			int p=read(),v=read();
			update(1,1,n,p,v);
		}
		printf("%d\n",solve(X,1,1,n));
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/ez_lcw/article/details/121884718