Bash Game-Plus

发布时间 2023-08-01 00:02:23作者: HEIMOFA

Bash Game-Plus 洛谷

题目描述

巴什博弈:有一堆 \(n\) 个物品,两名玩家轮流从中拿取物品。每次至少拿 \(1\) 个,至多拿 \(m\) 个,不能不拿,最终将物品拿完者获胜。

我们给这个游戏增加一些规则:

有一堆 \(n\) 个物品,甲和乙轮流从中拿取物品,甲先拿。每次至少拿 \(1\) 个,至多拿 \(m\) 个,最终将物品拿完者获胜。
现在新加入一条规则:也可以不拿,但每当有一名玩家选择不拿物品时,接下来的 \(k\) 次操作中两名玩家都不可以不拿。

举个例子,当 \(k=3\) 时,如果甲在某一次操作中没有拿物品,那么接下来乙、甲、乙三轮都必须拿至少 \(1\) 件物品。然后又轮到甲了,这次甲就可以再次选择不拿。

甲乙两人一共进行了 \(t\) 次游戏。对于每次游戏,你需要告诉甲他有没有必胜策略。


输入格式

第一行两个正整数 \(t,op\)\(op=1\) 时取消新增加的规则,但是也需要正常读入 \(k\)

接下来的 \(t\) 行,每行三个正整数 \(n,m,k\)


输出格式

对于每轮游戏,如果甲有必胜策略,那么输出 Yes。否则输出 No


样例输入

6 0
2 2 2
3 2 2
4 2 2
7 2 3
13 2 6
14 2 6

样例输出

Yes
Yes
No
Yes
Yes
No

提示

对于所有数据:\(t\le10^5\)\(1\le n,m,k\le10^{18}\)


分析题目,如果去掉了 \(k\) 这个限制,那么就会变成一个大家很熟悉的问题。

这个问题的结论如下(知道的可以跳过)。

\((m+1)\mid n\),先手必败,否则先手必胜。

策略就是两个人每次拿到的和都为 \((m+1)\),先手肯定拿了前半段,后手拿了后半段。

这样整个 \(n\) 就被分成了若干个 \((m+1)\) 段。

如果刚好整除,那么说明后手拿了最后一段的后半段,这肯定拿到了最后一个,所这个时候以先手必败。

先手必胜只需要一开始拿走 \(n\mod (m+1)\) 即可。

那么添加回 \(k\) 这个限制,接着便要尽可能的靠近上面的思路。

我们知道,先手在某一次不拿以后,肯定要过了 \(\lfloor \frac{k}{2} \rfloor\) 回才能再一次使用。

而过的这 \(\lfloor \frac{k}{2} \rfloor\) 回可以直接用前面的策略,也就是这些回每一回都会被拿走 \((m+1)\) 个物品。

接着我们便可以把 \(n\) 分成若干个 \((\lfloor \frac{k}{2} \rfloor) \times (m+1)\) 段。

但实际上并不是,首先我们必须清楚,在前面的问题里,分成若干个 \((m+1)\) 段是因为这些在下一次的选择权都在先手手上,而这个分段下一次的选择权却在后手手上。

所以我们把 \(n\) 分成若干个 \((\lfloor \frac{k}{2} \rfloor) \times (m+1)+1\) 段,这样每一段保证了先手必败,也有下一次的选择权

接着便可以用 \(n\mod ((\lfloor \frac{k}{2} \rfloor) \times (m+1)+1)\),如下判断即可。

如果模数小于等于 \(1\),可以直接判断,如果模数为 \(1\) 那么先手必胜,否则必败。

如果模数大于 \(1\),那么这不就是 \(k> n\) 的情况吗,模一下 \((m+1)\) 再判断等不等于 \(1\) 即可。

小证明:

如果最后模数为 \(0\),那么一来就不取,根据一开始的结论,且我方为后手,所以必胜。

如果最后模数大于 \(1\),那么一来先取一个,剩下的肯定不能被整除,且我方为接下来的后手(以剩下的为视角),理论上必败,但是还有一次不取的机会。

如果对方不取,那么我方变为先手(以不取过后,剩下的为视角),又因为不能整除,所以必胜。

如果对方要取,为了获胜,对方肯定会将剩下的数变为 \((m+1)\) 的倍数,此时选择不取,因为剩下的数为 \((m+1)\) 的倍数,且我方为后手(以不取过后,剩下的为视角),所以必胜。

这样你也知道为什么 \(1\) 不行了吧。

那么代码自然就出来了,时间复杂度 \(O(t)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;

void print(int x){
	if(x) printf("Yes\n");
	else printf("No\n");
	return ;
}
signed main()
{
	int t,op;scanf("%lld%lld",&t,&op);
	while(t--){
		int n,m,k;scanf("%lld%lld%lld",&n,&m,&k);
		if(op) print(n%(m+1));
		else if(k==1) print(1);
		else{
			__int128 round=(__int128)(m+1)*(k/2)+1;//这玩意竟然需要开__int128
			int check=n%round;
			if(check>1) check%=m+1,check=(check!=1);
			print(check);
		}
	}
	return 0;
}

一个你可能问的问题:为什么偏偏选择了 \(1\) 呢?

如果选择了其它的数,那么可能会导致大于等于 \((m+1)\),而 \(1\) 则没有这种烦恼。