Tyvj p1371 蛇灵迷宫 (博弈 输出路径)

蛇灵迷宫 
  描述 Description
    李元芳和闪灵在打斗中不慎掉入了蛇灵总坛地下的迷宫,十分恐惧~~~ 迷宫是由n个点组成的,他们停留在1号点,规定他们两个人在一起,每个人轮流决定下一步怎么走,并且只能从序号小的点走向序号大的点,最后无法决定接下来怎么走(走到死路)的人会被处死,另一个人得以逃生。
    在这个危机的时刻,拿到地图的李元芳必须以迅雷不及掩耳盗铃的速度判断出自己能否通过做出正确决策来逃生(既然对方是闪灵嘛!当然他每次做出的决策肯定是最好的),所以李元芳向会OI的你求救~~~请你判断出元芳能否通过做出正确决策来逃生,并且求出在他和闪灵同时做出正确决策的情况下,他们两人可能到达的点的编号。
输入格式 InputFormat
第一行三个数 n,m,f
n表示迷宫的点数 m表示迷宫的边数 f表示第一次作出决定的人(0-闪灵先 1-元芳先)
接下来m行 每行两个数x和y 表示编号为x的点和和编号为y的点是连通的
数据范围:
1<=n<=1000 1<=m<=10000 0<=f<=1
1<=x,y<=n
输出格式 OutputFormat
第一行一个数l (1表示最后元芳能活下来,0表示闪灵能活下来)
第二行按从小到大顺序打出两人可能到达的点的编号,中间用空格隔开
样例输入 SampleInput 
5 5 1
1 2
1 3
2 4
4 5
3 5
样例输出 SampleOutput 
7

思路:
    比较坑爹的一个题,此题的难点在于可能路径点的输出,并且要注意的是,路径不止一条,要输出的是所有可行路径经过点的并集。。
    方法,首先SG函数判断输赢,然后记忆化搜索确定路径点,方法如下
    要说求赢家第一步的选择,大家可能不陌生,只要一个公式即可,这儿其实类似的道理。但要说的是赢家做出选择(不唯一)后,输家的选择是任意的(因为不管输家怎么选择,赢家还是会赢,选择的关键在赢家),下一次赢家继续以输家选择的点作为起点做选择,以次类推。

代码:
#include <stdio.h>
#include <string.h>
#define N 1005

bool map[N][N];			// 记录关系
bool end[N];			// 标记出度为0的点(终点)
int sg[N];
bool ans[N];			// 标记所有要走的点(答案)
int dp[N][2];			// 用于记忆化搜索,记录之后的路径是否能走(能为1,不能为0,-1代表还未走过),二维是因为要分先后手(先手走过,后手不一定)
int n, m, f;

int getSG(int k);		// 获取SG值

bool dfs(int start, int k)			// 深搜获得路径,start为当前点下标,k为输赢家标识,0表示输家,1表示赢家
{
	if(dp[start][k] != -1)			// 检查之后的结点是否已经走过了
		return dp[start][k] == 0 ? 0 : 1;

	int i;
	bool ls, ok = 0;
	if(k == 1){						// 赢家选择
		for(i = start + 1; i <= n; i ++){
			if(map[start][i] && sg[i] < (sg[i] ^ sg[start])){
				if(end[i] == 0){					// 赢家走到终点,记录路径,返回1
					ans[i] = 1;
					ok = 1;
				}
				ls = dfs(i, 0);						// ls == 1 表示此路径可走通,标记路径上的点
				if(ls){
					ok = 1;
					ans[i] = 1;
				}
			}
		}
	}
	else{							// 输家选择, 因为必输,所以在先手选择正确的基础上,所以它的选择是随意的(但注意,这儿先手的选择不一定是对的)
		for(i = start + 1; i <= n; i ++)			// 判断赢家选择是否正确,不正确直接返回0
			if(map[start][i] && sg[i] < (sg[i] ^ sg[start]))
				return 0;
		for(i = start + 1; i <= n; i ++){
			if(map[start][i]){
				if(sg[i] < (sg[i] ^ sg[start]))		// 输家走到终点,表示赢家有选择错误,停止搜索,返回0
					return 0;
				ls = dfs(i, 1);
				if(ls){								// ls == 1 表示此路径可走通,标记路径上的点
					ok = 1;
					ans[i] = 1;
				}
			}
		}
	}

	return dp[start][k] = ok;		// 记录当前状态
}

int main()
{
	int x, y, i;
	while(scanf("%d%d%d", &n, &m, &f) != EOF){
		memset(sg, -1, sizeof(sg));				// 初始化
		memset(map, 0, sizeof(map));
		memset(end, 0, sizeof(end));
		memset(ans, 0, sizeof(ans));
		memset(dp, -1, sizeof(dp));
		while(m --){
			scanf("%d%d", &x, &y);
			if(x == y)
				continue;
			if(x > y){
				int temp = x;
				x = y;
				y = temp;
			}
			map[x][y] = 1;
			end[x] = 1;							// 记录出度
		}

		for(i = 1; i <= n; i ++){
			if(end[i] == 0)						// 出度为0的点为必败点,sg值为0
				sg[i] = 0;
		}

		getSG(1);

		int win = (sg[1] != 0) ? f : (f + 1) % 2;
		printf("%d\n", win);					// 输出赢家

		ans[1] = 1;
		dfs(1, sg[1] == 0 ? 0 : 1);

		for(i = 1; i <= n; i ++){				// 输出路径,注意空格处理
			if(ans[i]){
				printf("%d", i);
				break;
			}
		}
		for(i = i + 1; i <= n; i ++){
			if(ans[i])
				printf(" %d", i);
		}
		printf("\n");
	}

	return 0;
}

int getSG(int k)				// 获取SG值
{
	if(sg[k] != -1)
		return sg[k];

	bool hash[N];
	memset(hash, 0, sizeof(hash));
	for(int i = k + 1; i <= n; i ++){
		if(!map[k][i])
			continue;
		sg[i] = getSG(i);
		hash[sg[i]] = 1;
	}
	for(int j = 0; ; j ++)
		if(!hash[j])
			return sg[k] = j;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值