CF786A Berzerk

29 March 2019 | duanyll | Tags: OI 题解 博弈论

切着切着搜索水题就做到它了。鉴于我还没有正式的学过博弈论,就写篇题解纪念一下吧。

题意

有一个物品放在n个排成一圈的点上,初始放在第2到n号点,甲乙各有一个数集,每次操作时可以将这个物品向后移动s格(s是集合中的数),判断物品位于每个起始位置时,二人的胜负情况。

分析

结论:能转移到必败态的状态就是必胜态,只能转移到必胜态的状态就是必败态。(思考一下)

因此就可以从1号点开始倒着dfs,记得判平局(从1号点出发不能到达的就是平局)

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;

const int MAXN = 7010;
const int INF = 0x3f3f3f3f;

vector<int> s[2];
bool ans[MAXN][2];
bool vis[MAXN][2];
int cnt[MAXN][2];	//统计当前状态可以到达的必胜态的数量 
int n;

void dfs(int pos,int player){
	if(vis[pos][player]){
		return;
	}
	clog << pos << ' ' << player << endl;
	vis[pos][player] = true;
	int last = 1-player;
	for(int i = 0;i<s[last].size();i++){
		int v = (pos - s[last][i] + n - 1)%n + 1;
		if(v == 1){
			continue;
		}
		if(ans[pos][player]){
			cnt[v][last]++;
			if(cnt[v][last] == s[last].size()){
				ans[v][last] = false;
				dfs(v,last);
			}
		}else{
			ans[v][last] = true;
			dfs(v,last);
		}
	}
}

int main(){
	cin >> n;
	int k0;
	cin >> k0;
	for(int i = 1;i<=k0;i++){
		int x;
		cin >> x;
		s[0].push_back(x);
	}
	int k1;
	cin >> k1;
	for(int i = 1;i<=k1;i++){
		int x;
		cin >> x;
		s[1].push_back(x);
	}
	dfs(1,0);
	dfs(1,1);
	for(int i = 2;i<=n;i++){
		if(vis[i][0]){
			if(ans[i][0]){
				cout << "Win ";
			}else{
				cout << "Lose ";
			}
		}else{
			cout << "Loop ";
		}
	}
	cout << endl;
	for(int i = 2;i<=n;i++){
		if(vis[i][1]){
			if(ans[i][1]){
				cout << "Win ";
			}else{
				cout << "Lose ";
			}
		}else{
			cout << "Loop ";
		}
	}
	cout << endl;
}