【XSY3949】取石子游戏(博弈,线段树)

题面

取石子游戏

题解

我们把一个有 \(n\) 个石子, Alice 每次能拿 \(a\) 个, Bob 每次能拿 \(b\) 个的堆称为状态 \((n,a,b)\)。石子数太大的时候不利于分析,尝试简化一下:

可以证明状态 \((n,a,b)\) 的结果同 \((n\bmod (a+b),a,b)\) 状态的结果相同。

所以我们只需对每一个 \(n\gets n\bmod(a+b)\),然后只考虑 \(n<a+b\) 的情况。

注意两人此时的步数是相同的,所以他们各自的最优策略是最大化“他们自己接下来走的步数-另一个人接下来走的步数”,不妨设这个值对于两人而言是 \(s_a,s_b\)

对于每个堆分类讨论:

  • \(n<a,b\),那么两人都不能取这个堆,直接扔掉。

  • \(a\leq n<b\)\(b\leq n<a\)。不妨是 \(a\leq n<b\)\(b\leq n<a\) 的情况同理),此时这一堆只有 Alice 可以取,那么直接对 \(s_a\) 贡献 \(\left\lfloor\dfrac{n}{a}\right\rfloor\)

  • \(a,b\leq n\),注意到 \(n<a+b\),所以此时只要有一个人在这个堆里取了石子,另一个人就不能取了,换言之前者 “占领” 了这堆石子。那么对于 Alice 来说,如果她 “占领” 了这堆石子,那么她可以多走 \(\left\lfloor\dfrac{n}{a}\right\rfloor\) 步,Bob 同理。

    可能有人觉得此时 Alice 按 \(\left\lfloor\dfrac{n}{a}\right\rfloor\) 从大到小 “占领”、Bob 按 \(\left\lfloor\dfrac{n}{b}\right\rfloor\) 从大到小 “占领” 就可以了,但这并不符合最优策略。

    不妨设满足 \(a,b\leq n<a+b\) 的堆一共有 \(m\) 堆。对于第 \(i\) 堆,设如果先手 “占领” 了这堆石子,先手可以多走 \(A_i\) 步,如果对手 “占领” 了这堆石子,对手可以多走 \(B_i\) 步。设 \(A_i\) 的总和为 \(SA\)\(B_i\) 的总和为 \(SB\)

    假设先手取的堆的编号为 \(p_1,p_2,\cdots,p_k\),那么先手和后手的步数差等于:

    \[\begin{aligned} D_A=&A_{p_1}+\cdots+A_{p_k}-(B-B_{p_1}-\cdots-B_{p_k})\\ =&(A_{p_1}+B_{p_1})+\cdots+(A_{p_k}+B_{p_k})-SB \end{aligned} \]

    那么后手和先手的步数差也可以同理推出:\(D_B=(A_{p_1}+B_{p_1})+\cdots+(A_{p_k}+B_{p_k})-SA\)

    由于双方都需要优化他们各自的 \(D\),而 \(SA\)\(SB\) 又是一个定值,所以他们都会按 \((A_i+B_i)=\left\lfloor\dfrac{n}{a}\right\rfloor+\left\lfloor\dfrac{n}{b}\right\rfloor\) 从大到小轮流 “占领”。

    注意占领也是需要步数的,那么如果 \(m\) 为奇数,先手会多耗费一步占领。所以为了维护这个东西,不妨把 \(A_i\gets A_i-1\)\(B_i\gets B_i-1\)

    至于维护这个东西,先把 \((A_i+B_i)\) 离线下来排序再用线段树即可。

所以三种情况我们都处理完了。

代码如下:

#include<bits/stdc++.h>

#define N 200010
#define lc (k<<1)
#define rc (k<<1|1)
#define int 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,x[N],a[N],b[N];
int suma[N],sumb[N];
int tot,p[N],q[N],rk[N];
int s[N],pa[N],pb[N];
int sum[N<<2][2];
bool size[N<<2];

bool cmp(int a,int b)
{
	return p[a]>p[b];
}

void up(int k)
{
	size[k]=size[lc]^size[rc];
	if(size[lc])
	{
		sum[k][1]=sum[lc][1]+sum[rc][0];
		sum[k][0]=sum[lc][0]+sum[rc][1];
	}
	else
	{
		sum[k][1]=sum[lc][1]+sum[rc][1];
		sum[k][0]=sum[lc][0]+sum[rc][0];		
	}
}

void update(int k,int l,int r,int x,int y)
{
	if(l==r)
	{
		size[k]=1;
		sum[k][1]=y;
		sum[k][0]=0;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) update(lc,l,mid,x,y);
	else update(rc,mid+1,r,x,y);
	up(k);
}

signed main()
{
//	freopen("ex_C2.in","r",stdin);
//	freopen("ex_C2_my.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)
	{
		x[i]=read(),a[i]=read(),b[i]=read();
		x[i]%=(a[i]+b[i]);
		suma[i]=suma[i-1],sumb[i]=sumb[i-1];
		s[i]=s[i-1],pa[i]=pa[i-1],pb[i]=pb[i-1];
		if(x[i]<a[i]&&x[i]<b[i]) continue;
		if(x[i]<a[i])
		{
			sumb[i]+=x[i]/b[i];
			continue;
		}
		if(x[i]<b[i])
		{
			suma[i]+=x[i]/a[i];
			continue;
		}
		p[i]=x[i]/a[i]+x[i]/b[i]-2,q[++tot]=i;
		s[i]+=p[i],pa[i]+=x[i]/a[i]-1,pb[i]+=x[i]/b[i]-1;
	}
	sort(q+1,q+tot+1,cmp);
	for(int i=1;i<=tot;i++) rk[q[i]]=i;
	bool tag=0;
	for(int i=1;i<=n;i++)
	{
		if(rk[i]) update(1,1,tot,rk[i],p[i]),tag^=1;
		int fi=sum[1][1];
//		int se=(s[i]-sum[1])/2;
		int tmp=suma[i]-sumb[i]+fi-pb[i];
		bool Af=(tmp>0||(tmp==0&&tag));
		tmp=sumb[i]-suma[i]+fi-pa[i];
		bool As=(tmp<0||(tmp==0&&(!tag)));
		if(Af&&As) puts("Alice");
		else if((!Af)&&(!As)) puts("Bob");
		else if(Af) puts("First");
		else if(As) puts("Second");
	}
	return 0;
}
/*
4
3 2 4
4 5 2
1 1 2
5 3 4
*/
posted @ 2022-10-30 14:26  ez_lcw  阅读(36)  评论(0编辑  收藏  举报