专栏文章

题解:P7656 [BalticOI 1996] A NUMBER GAME (Day 2)

P7656题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@minqms0c
此快照首次捕获于
2025/12/02 06:45
3 个月前
此快照最后确认于
2025/12/02 06:45
3 个月前
查看原文

题意

最开始有两个数 nnmm,有两个人 A 和 B 轮流进行操作,A 先手。每次操作的人可以选择 1kmin(n,m)1 \le k \le \min(n,m),让 nn 变成 nkn-k。但是选择的 kk 不能是之前自己或其他人选过的。最先无法选数的人输掉。
给出 nnmm,问:谁会赢;对于所有 AA 第一步能选择的 kk,给出 AA 必胜或 BB 接下来选的 kk 是多少时必胜。有多个答案时输出最大的那个。

题解

尊重随机跳题。
看到数据范围 m20m \le 20 就大彻大悟了。这样我们可以用一个 2020 位的二进制数来保存一局游戏的某一状态,一个 int 就能存下。
在本题中,我们认为 xx 的二进制下第 ii 位表示当前有没有选过 ii。为了方便使用 x>>i&1 判断,这里的第 ii 位实际上是二进制从低位向高位数第 i+1i+1 位。
对于某一个状态 xx,我们可以统计出当前的 nn 是多少,即 n=ni=1m[x2imod2=1]in'=n-\sum_{i=1}^{m}[\lfloor\frac{x}{2^i}\rfloor \bmod 2=1]i
不难想到记忆化搜索。枚举下一步选的数是 kk,如果是对方必输,那当前状态就是必赢局面;如果不论怎么选都没有对方必输的局面,那当前状态就是必输局面。边界条件是走不了下一步,那当前状态就是必输局面,可以和“没有对方必输的局面”情况合并。
判断能不能赢的事情解决了,但题目还要问怎么选取 kk 才能必赢这样的问题。我们的记忆化搜索还要额外维护一个信息:如果必赢,需要如何选取 kk 才能保证必赢。对于那个取最大值的要求,从大到小枚举 kk 即可。
代码:
CPP
#include<iostream>
using namespace std;
int n,m;
int mem[1<<21|1];//=0表示失败;非零数字表示需要选择mem[x]就会必赢
bool vis[1<<21|1];
int dfs(int x){
	if(vis[x])	return mem[x];
	int curn=n;
	for(int i=1;i<=m;i++){
		if(x>>i&1){
			curn-=i;
		}
	}
	for(int i=min(curn,m);i>=1;i--){
		if(x>>i&1)	continue;
		int res=dfs(x|(1<<i));
		if(res==0){
			vis[x]=true;
			return mem[x]=i;
		}
	}
	vis[x]=true;
	return mem[x]=0;
}
int main(){
	cin >> n >> m;
	if(dfs(0)){
		cout << " A wins\n";
	} else{
		cout << " B wins\n";
	}
	for(int i=1;i<=m && i<=n;i++){
		if(i<=9)	cout << " ";
		cout << i << " ";
		int res=dfs(1<<i);
		if(res==0){
			cout << "winning" << "\n";
		} else{
			cout << res << "\n";
		}
	}
	return 0;
}

备注

这题的输出格式要求输出的前十行都有空格,所以代码会有奇怪的判断和空格。来源

评论

0 条评论,欢迎与作者交流。

正在加载评论...