公开游戏、基于有向图的游戏

news2024/11/28 0:31:06

目录

〇,背景

一,公开游戏、策梅洛定理

1,公开游戏

2,策梅洛定理

二,有向图游戏

1,狭义有向图游戏

2,广义有向图游戏

3,狭义有向图游戏的SG数

4,Bash Game

力扣 292. Nim 游戏(正向Bash)

51Nod 1066 Bash游戏(正向Bash)

51Nod 1067 Bash游戏 V2(正向拓展Bash)

51Nod 1068 Bash游戏 V3(正向拓展Bash)

三,公平游戏、公平组合游戏、Sprague-Grundy定理

1,公平游戏

2,公平组合游戏

3,Sprague-Grundy定理

4,Nim Game

HDU 2176 取(m堆)石子游戏 (正向Nim)

POJ 1704 Georgia and Bob (正向Nim的变形)

FZU 1928 硬币翻转游戏(正向1.5维Nim)

HDU 1907 John (反向Nim)

力扣 1908. Nim 游戏 II

5,Green Hackenbush

力扣 2005. 斐波那契树的移除子树游戏

四,有向有环图游戏

力扣 913. 猫和老鼠


〇,背景

本文收录基于有向图的博弈,提供通用的解法。

本文涉及5种博弈类型:公开游戏、有向图游戏、有向有环图游戏、公平游戏、公平组合游戏

五者的关系:公开游戏包括有向图游戏,有向图游戏包括公平游戏,公平游戏包括公平组合游戏,而有向有环图游戏是单独的

其中有向图游戏指的是基于有向无环图的游戏,有向有环图游戏是基于可能有环的有向图的游戏。

PS:在有向无环图上,我们定义只有胜负的游戏,而在可能有环的有向图上,我们定义有胜负平三种结局的游戏。

一,公开游戏、策梅洛定理

1,公开游戏

若一个游戏满足如下条件:

  1. 双人、回合制;
  2. 信息完全公开(perfect information);
  3. 无随机因素(deterministic);
  4. 必然在有限步内结束;
  5. 没有平局;

则称之为公开游戏。

2,策梅洛定理

策梅洛定理:公开游戏中的任何一个状态,要么先手有必胜策略,要么后手有必胜策略(下文把这两种状态分别称为“胜态”、“败态”)。

常见的牌类游戏大多不满足条件2、3。

常见的棋类游戏(如井字棋、五子棋、围棋、象棋、跳棋)大多满足条件2、3,在正式竞技中也会通过禁止循环的方式保证条件4,但不一定满足条件5。

二,有向图游戏

1,狭义有向图游戏

在一张有向无环图中,某一个节点上有一颗棋子,两名玩家轮流将棋子沿有向边移动,最先不能移动的一方输。

这里的绿色节点是败态,粉红色是胜态,整个图可以分为4层:

自底向上推导:

底层是最右边的最0层,是败态。

有一条边指向任意败态节点的就是胜态节点,所以第1层的2个节点是胜态。

所有边都指向胜态节点的节点的就是败态节点,所以第2层的1个节点是败态。

第3层的1个节点是胜态。

这个分层推导的例子,可以推广到所有的有向无环图,最终每个节点都有一个非负层数,层数的奇偶性直接决定是胜态还是败态。

2,广义有向图游戏

(1)推广1

在狭义有向图游戏的基础上进行推广,对于任意有向有环图,定义其中一些节点为胜或者负,两名玩家轮流将棋子沿有向边移动。

要想保证最终一定会走到定义了胜负的节点,需要保证出度为0的点一定定义了胜负,出度不为0的每一个点都可以定义或不定义胜负。

这一类游戏全部可以转化成狭义有向图游戏,具体方法是2步:

第1步,把所有定义了胜负的节点,出度置为0,即从他们出发的有向边全部删除。

第2步,新增1个定义为负的节点X,把所有定义成胜的节点,出度置为1,即从他们连一条有向边到X。

这样就转化成了狭义有向图游戏。

(2)推广2

因为有向图本身就是个建模工具,能表达丰富的信息,所以很多博弈都可以转化成有向图游戏(转化成狭义有向图游戏,或有向图游戏推广1),我们把这些游戏都称为广义有向图游戏。

一般说有向图游戏,指的都是广义有向图游戏,属于公开游戏的一种。

公开游戏可能不是广义有向图游戏,因为公开游戏可以不满足每步的走法数有限

3,狭义有向图游戏的SG数

SG数的定义:

SG(x)=mex\{SG(y)| x\rightarrow y\}

其中x->y表示存在x指向y的一条边,mex(S)表示不在集合S中的最小自然数。

SG为0则是败态,SG>0则是胜态。

无论是分层的方式,还是SG数,都可以通过遍历来求出所有状态是胜态还是负态。

4,Bash Game

有一堆石子共有N个。A B两个人轮流拿,A先拿。每次最少拿1颗,最多拿K颗。

正向:谁拿完就判胜,规律是N是K+1的倍数时先手负,否则先手胜

反向:谁拿完就判负,规律是N-1是K+1的倍数时先手负,否则先手胜

力扣 292. Nim 游戏(正向Bash)

题目:

你和你的朋友,两个人一起玩 Nim 游戏:桌子上有一堆石头,每次你们轮流拿掉 1 - 3 块石头。 拿掉最后一块石头的人就是获胜者。你作为先手。

你们是聪明人,每一步都是最优解。 编写一个函数,来判断你是否可以在给定石头数量的情况下赢得游戏。

示例:

输入: 4
输出: false 
解释: 如果堆中有 4 块石头,那么你永远不会赢得比赛;
     因为无论你拿走 1 块、2 块 还是 3 块石头,最后一块石头总是会被你的朋友拿走。

这分明是Bash Game,不是Nim Game

代码:

class Solution {
public:
    bool canWinNim(int n) {
        return n%4;
    }
};

51Nod 1066 Bash游戏(正向Bash)

有一堆石子共有N个。A B两个人轮流拿,A先拿。每次最少拿1颗,最多拿K颗,拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N和K,问最后谁能赢得比赛。

例如N = 3,K = 2。无论A如何拿,B都可以拿到最后1颗石子。

Input

第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 10000) 第2 - T + 1行:每行2个数N,K。中间用空格分隔。(1 <= N,K <= 10^9)

Output

共T行,如果A获胜输出A,如果B获胜输出B。

Sample Input

4
3 2
4 2
7 3
8 3

Sample Output

B
A
A
B
#include<iostream>
using namespace std;

int main()
{
    int a,b;
    cin >> a;
    while (cin >> a >> b) {
        if (a % ++b)cout << "A\n";
        else cout << "B\n";
    }
    return 0;
}

51Nod 1067 Bash游戏 V2(正向拓展Bash)

有一堆石子共有N个。A B两个人轮流拿,A先拿。每次只能拿1,3,4颗,拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N,问最后谁能赢得比赛。

例如N = 2。A只能拿1颗,所以B可以拿到最后1颗石子。

Input

第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 10000) 第2 - T + 1行:每行1个数N。(1 <= N <= 10^9)

Output

共T行,如果A获胜输出A,如果B获胜输出B。

Sample Input

3
2
3
4

Sample Output

B
A
A

思路:以7为周期

#include<iostream>
using namespace std;

int main()
{
    int a;
    cin >> a;
    while (cin >> a) {
        if (a % 7 == 0 || a % 7 == 2)cout << "B\n";
        else cout << "A\n";
    }
    return 0;
}

51Nod 1068 Bash游戏 V3(正向拓展Bash)

有一堆石子共有N个。A B两个人轮流拿,A先拿。每次拿的数量只能是2的正整数次幂,比如(1,2,4,8,16....),拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N,问最后谁能赢得比赛。

例如N = 3。A只能拿1颗或2颗,所以B可以拿到最后1颗石子。(输入的N可能为大数)

Input

第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 1000) 第2 - T + 1行:每行1个数N。(1 <= N <= 10^1000)

Output

共T行,如果A获胜输出A,如果B获胜输出B。

Sample Input

3
2
3
4

Sample Output

A
B
A

思路:2的幂其实就是mod 3等于1或2,所以只需要看总数是不是3的倍数

#include<iostream>
using namespace std;

int main()
{
    int a;
    cin >> a;
    string s;
    while (a--) {
        cin >> s;
        int k = 0;
        for (int i = 0; i < s.length(); i++)k += s[i];
        if (k % 3 == 0)cout << "B\n";
        else cout << "A\n";
    }
    return 0;
}

三,公平游戏、公平组合游戏、Sprague-Grundy定理

1,公平游戏

若一个游戏满足以下条件:
  1. 双人、回合制;
  2. 信息完全公开(perfect information);
  3. 无随机因素(deterministic);
  4. 必然在有限步内结束,且每步的走法数有限(finite);
  5. 没有平局;
  6. 双方可采取的行动及胜利目标都相同(impartial);
  7. 这个胜利目标是自己亲手达成终局状态,或者说走最后一步者为胜(normal play);

则称之为公平游戏。

公平游戏都属于有向图游戏

虽然有向图游戏中的胜负很明确,但是如果图非常大,则遍历需要的时间很长。

2,公平组合游戏

如果一个公平游戏的每个状态,都可以表示成若干个子状态的组合,且游戏每一次操作,都只能对一个子状态进行操作,则称之为公平组合游戏。

公平组合游戏都属于公平游戏

公平游戏可以表示成有向图,而公平组合游戏可以表示成若干个有向连通分量。

3,Sprague-Grundy定理

公平组合游戏中,一个状态的SG数,是所有子状态的SG数的异或和。

PS:只要学过NIM博弈,就很好理解了,每个公平组合游戏都可以映射成一个NIM博弈,其中每个有向连通分量,就是NIM博弈中的一个石头堆,整个有向图就是NIM博弈中的所有石头堆。

4,Nim Game

Nim is a mathematical game of strategy in which two players take turns removing (or "nimming") objects from distinct heaps or piles. On each turn, a player must remove at least one object, and may remove any number of objects provided they all come from the same heap or pile. Depending on the version being played, the goal of the game is either to avoid taking the last object or to take the last object.

正向Nim游戏就是谁拿完最后一堆获胜,规律很简单,只要保持自己每次拿完之后,所有堆的数量异或和为0 即可。

即:异或和为0时先手负,不为0时先手胜

反向是谁拿完就判负,规律比较复杂:如果至少有一堆数量大于1,那么,异或和为0时先手负,不为0时先手胜,否则,异或和为0时先手胜,不为0时先手负。

Nim游戏的SG数:一堆石头的SG数就是石头数。

HDU 2176 取(m堆)石子游戏 (正向Nim)

题目:
Description

m堆石子,两人轮流取.只能在1堆中取.取完者胜.先取者负输出No.先取者胜输出Yes,然后输出怎样取子.例如5堆 5,7,8,9,10先取者胜,先取者第1次取时可以从有8个的那一堆取走7个剩下1个,也可以从有9个的中那一堆取走9个剩下0个,也可以从有10个的中那一堆取走7个剩下3个.
Input

输入有多组.每组第1行是m,m<=200000. 后面m个非零正整数.m=0退出. 
Output

先取者负输出No.先取者胜输出Yes,然后输出先取者第1次取子的所有方法.如果从有a个石子的堆中取若干个后剩下b个后会胜就输出a b.参看Sample Output. 
Sample Input

2
45 45
3
3 6 9
5
5 7 8 9 10
0
Sample Output

No
Yes
9 5
Yes
8 1
9 0
10 3

Nim游戏早就被研究透了,我就不扯了,直接说结论。

取r为所有数的异或总值,如果r为0那么先手败,否则先手胜。

如果先手胜的话,他的策略是调整1个数,使得所有数的异或总值为0

也就是说,取某个x,将x变成x^r

只要x大于x^r,这个操作就是允许的。

这样的x,至少存在一个,可能会有很多个,本题要我们全部输出。

代码:
 

#include<iostream>
using namespace std;

int num[200005];

int main()
{	
	int m, r;
	while (scanf("%d",&m))
	{
		if (m == 0)break;
		r = 0;
		for (int i = 0; i < m; i++)
		{
			scanf("%d", &num[i]);
			r = r^num[i];
		}
		if (r == 0)printf("No\n");
		else
		{
			printf("Yes\n");
			for (int i = 0; i < m; i++)if (num[i] >(num[i] ^ r))
				printf("%d %d\n", num[i], num[i] ^ r);
		}
	}
	return 0;
}

POJ 1704 Georgia and Bob (正向Nim的变形)

题目:


Georgia and Bob decide to play a self-invented game. They draw a row of grids on paper, number the grids from left to right by 1, 2, 3, ..., and place N chessmen on different grids, as shown in the following figure for example: 


Georgia and Bob move the chessmen in turn. Every time a player will choose a chessman, and move it to the left without going over any other chessmen or across the left edge. The player can freely choose number of steps the chessman moves, with the constraint that the chessman must be moved at least ONE step and one grid can at most contains ONE single chessman. The player who cannot make a move loses the game.

Georgia always plays first since "Lady first". Suppose that Georgia and Bob both do their best in the game, i.e., if one of them knows a way to win the game, he or she will be able to carry it out. 

Given the initial positions of the n chessmen, can you predict who will finally win the game? 
Input
The first line of the input contains a single integer T (1 <= T <= 20), the number of test cases. Then T cases follow. Each test case contains two lines. The first line consists of one integer N (1 <= N <= 1000), indicating the number of chessmen. The second line contains N different integers P1, P2 ... Pn (1 <= Pi <= 10000), which are the initial positions of the n chessmen.
Output
For each test case, prints a single line, "Georgia will win", if Georgia will win the game; "Bob will win", if Bob will win the game; otherwise 'Not sure'.
Sample Input
2
3
1 2 3
8
1 5 6 7 9 12 14 17
Sample Output
Bob will win
Georgia will win

题意:

有N个棋子排成一排,位置分别是所给定的整数,双方轮流把其中一个棋子往左移,但是不能超过左边的棋子,也不能移到同一个位置,只能移到它的右边一格。

问先手还是后手有必胜策略。

思路:

两两一组,如果是奇数个,最左边的单独一组,通过差分,化成Nim博弈

代码:

#include<iostream>
#include<algorithm>
using namespace std;
 
int main()
{
	int T, n, s, l[1005];
	cin >> T;
	while (T--)
	{
		cin >> n;
		s = 0;
		for (int i = 0; i < n; i++)cin >> l[i];
		sort(l, l + n);
		if (n % 2)s = l[0] - 1;
		for (int i = n % 2; i < n; i += 2)s ^= l[i + 1] - l[i] - 1;
		if (s)cout << "Georgia will win\n";
		else cout << "Bob will win\n";
	}
	return 0;
}

FZU 1928 硬币翻转游戏(正向1.5维Nim)

题目:

Description

Alice和Bob决定玩一种硬币翻转游戏。游戏的最初有n * m个硬币被放在一个棋盘上,有的正面朝上,有的反面朝上,当游戏开始的时候,他们轮流对硬币进行操作。
游戏的规则是这样的,当轮到一个人操作的时候,他必须挑选两枚硬币,将它们分别翻转(不是交换),同时这两枚硬币必须满足以下条件:

1. 两枚硬币必须在同一行或同一列

2. 两枚硬币中最右下的那枚必须是从正面翻转到反面

当一方无法做出满足上面要求的操作时,这一方就输了,游戏结束。

比如,当游戏初始状态是棋盘1时,Alice翻转完2枚硬币,可以将其变成 棋盘2的状态或棋盘3的状态。

Alice总是第一个操作,假设Alice和Bob都采取最优策略来玩这个游戏,请 你写一个程序判断Alice是否能赢。

Input

首先第一行为T,表示有T组数据。接下来为每组数据的结构:
每组测试数据的第一行有两个整数n和m (2 <= n, m <= 1000),代表棋盘上排列着n行m列的硬币,接下来n行,每行 m个字符描述硬币的初始状态,’F’代表硬币正面朝上,’B’代表硬币反面朝上。

Output

对于每组数据输出一行先输出组数(从1开始),接下来如果Alice能赢,输出YES,否则输出NO.
Sample Input

2
2 2
FB
BB
2 2
BF
BB
Sample Output

Case 1: NO
Case 2: YES

我只能说这个OJ绝对有毒啊

#include<iostream>
using namespace std;

int main()
{
	int t, n, m, ans = 0;
	cin >> t;
	char c;
	for (int cas = 1; cas <= t; cas++)
	{
		cin >> n >> m;
		for (int i = 0; i < n; i++)for (int j = 0; j < m; j++)
		{
			cin >> c;
			if (c == 'F')ans ^= (i^j);
		}
		if (ans)cout << "Case " << cas << ": " << "YES" << endl;
		else cout << "Case " << cas << ": " << "NO" << endl;
	}
	return 0;
}

HDU 1907 John (反向Nim)

Little John is playing very funny game with his younger brother. There is one big box filled with M&Ms of different colors. At first John has to eat several M&Ms of the same color. Then his opponent has to make a turn. And so on. Please note that each player has to eat at least one M&M during his turn. If John (or his brother) will eat the last M&M from the box he will be considered as a looser and he will have to buy a new candy box.

Both of players are using optimal game strategy. John starts first always. You will be given information about M&Ms and your task is to determine a winner of such a beautiful game.
 

Input

The first line of input will contain a single integer T – the number of test cases. Next T pairs of lines will describe tests in a following format. The first line of each test will contain an integer N – the amount of different M&M colors in a box. Next line will contain N integers Ai, separated by spaces – amount of M&Ms of i-th color.

Constraints:
1 <= T <= 474,
1 <= N <= 47,
1 <= Ai <= 4747
 

Output

Output T lines each of them containing information about game winner. Print “John” if John will win the game or “Brother” in other case.
 

Sample Input

2
3
3 5 1
1
1

Sample Output

John
Brother

思路:

flag表示至少有1堆的数量大于1,s表示异或和,那么if (flag ^ (s > 0))则后手胜,否则先手胜。

代码:

#include<iostream>
using namespace std;

int main()
{
    int n, a[50];
    cin >> n;
    while (cin >> n) {
        int flag = 0, s = 0;
        for (int i = 0; i < n; i++) {
            cin >> a[i];
            if (a[i] > 1)flag = 1;
            s ^= a[i];
        }
        if (flag ^ (s > 0))cout << "Brother\n";
        else cout << "John\n";
    }
    return 0;
}

力扣 1908. Nim 游戏 II

Alice 和 Bob 交替进行一个游戏,由 Alice 先手。

在游戏中,共有 n 堆石头。在每个玩家的回合中,玩家需要 选择 任一非空石头堆,从中移除任意 非零 数量的石头。如果不能移除任意的石头,就输掉游戏,同时另一人获胜。

给定一个整数数组 piles ,piles[i] 为 第 i 堆石头的数量,如果 Alice 能获胜返回 true ,反之返回 false 。

Alice 和 Bob 都会采取 最优策略 。

示例 1:

输入:piles = [1]
输出:true
解释:只有一种可能的情况:
- 第一回合,Alice 移除了第 1 堆中 1 块石头。piles = [0]。
- 第二回合,Bob 没有任何石头可以移除。Alice 获胜。
示例 2:

输入:piles = [1,1]
输出:false
解释:可以证明,Bob一定能获胜。一种可能的情况:
- 第一回合,Alice 移除了第 1 堆中 1 块石头。 piles = [0,1]。
- 第二回合,Bob 移除了第 2 堆中 1 块石头。 piles = [0,0]。
- 第三回合,Alice 没有任何石头可以移除。Bob 获胜。
示例 3:

输入:piles = [1,2,3]
输出:false
解释:可以证明,Bob一定能获胜。一种可能的情况:
- 第一回合,Alice 移除了第 3 堆中 3 块石头。 piles = [1,2,0]。
- 第二回合,Bob 移除了第 2 堆中 1 块石头。 piles = [1,1,0]。
- 第三回合,Alice 移除了第 1 堆中 1 块石头。piles = [0,1,0]。
- 第四回合,Bob 移除了第 2 堆中 1 块石头。 piles = [0,0,0]。
- 第三回合,Alice 没有任何石头可以移除。Bob 获胜。
 

提示:

n == piles.length
1 <= n <= 7
1 <= piles[i] <= 7
 

进阶:你能想出一个 线性时间 的解决方案吗?虽然这一答案可能超出了面试所需的范围,但了解它可能会很有趣。

class Solution {
public:
    bool nimGame(vector<int>& piles) {
        int ans=0;
        for(auto x:piles)ans^=x;
        return ans;
    }
};

5,Green Hackenbush

Hackenbush游戏是通过移除一个有根图的某些边并自动不和根相连的部分,最终没有边可移除则判负。

Green Hackenbush,每条边都是绿色的,双方都可以操作。

Blue-Red Hackenbush,有些边是蓝色,有些边是红色,而玩家一只能删除蓝色边,玩家二只能删除红色边。

力扣 2005. 斐波那契树的移除子树游戏

斐波那契树是一种按这种规则函数 order(n) 创建的二叉树:

  • order(0) 是空树。
  • order(1) 是一棵只有一个节点的二叉树。
  • order(n) 是一棵根节点的左子树为 order(n - 2) 、右子树为 order(n - 1) 的二叉树。

Alice 和 Bob 在玩一种关于斐波那契树的游戏,由 Alice 先手。在每个回合中,每个玩家选择一个节点,然后移除该节点其子树。只能删除根节点 root 的玩家输掉这场游戏。

给定一个整数 n,假定两名玩家都按最优策略进行游戏,若 Alice 赢得这场游戏,返回 true 。若 Bob 赢得这场游戏,返回 false 。

一棵二叉树的子树 tree 是由 tree 中某个节点及其所有后代节点组成的树。树 tree 也可当作自身的子树。

示例 1:

输入: n = 3
输出: true
解释:
Alice 移除右子树中的节点 1。
Bob 要么移除左子树中的 1,要么移除右子树中的 2。
Alice 可以移除 Bob 未移除的任意节点。
Bob 只能删除根节点 3,所以 Bob 输了。
返回 true,因为 Alice 赢了。

示例 2:

输入: n = 1
输出: false
解释:
Alice 只能移除根节点 1, 所以 Alice 输了。
返回 false,因为 Alice 输了。

示例 3:

输入: n = 2
输出: true
解释:
Alice 删除节点 1.
Bob 只能删除根节点 2,所以 Bob 输了。
返回 true,因为 Alice 赢了。

提示:

  • 1 <= n <= 100

思路:Green Hackenbush的一个特例。

如果是树而不是图,那么sg函数的递推式就是sg(root) = (sg(left)+1) ^ (sg(right)+1)

所以本题的sg值就是0 1 3 6 3 3 0 5 7 14 7 7 0 9 11 6 11 0 13......

class Solution {
public:
    bool findGameWinner(int n) {
        return n%6-1;
    }
};

四,有向有环图游戏

在一张可能有环的有向图中,某一个节点上有一颗棋子,两名玩家轮流将棋子沿有向边移动,要么走到预先定义了胜负平的节点,要么沿着环移动判定为平局。

策略:

(1)能走到负节点,那就走到负节点,当前就是胜节点

(2)如果只能走到胜节点,那我就是负节点

(3)反复运用前2条规则之后,还没有确定的节点,都是平节点,包括有环的情况。

按照这个策略,其实求解过程也不难。

class SG2 {
public:
	//输入每个节点的出度out, 反图rg,已知胜1负-1平0的节点集s,节点从0开始编号
	static void solve(vector<int>&out, map<int, vector<int>> &rg, map<int, int>&s)
	{
		vector<int>visit(out.size());
		vector<int>v1, v2;
		for (auto si : s) {
			visit[si.first] = 1;
			if (si.second == -1)v1.push_back(si.first);
			if (si.second == 1)v2.push_back(si.first);
		}
		while (!v1.empty() || !v2.empty())fresh(v1, v2, s, visit, rg,out);
		for (int i = 0; i < out.size(); i++)s[i];
	}
private:
	static void fresh(vector<int>&v1, vector<int>&v2, map<int, int>&s, vector<int>&visit, map<int, vector<int>> &rg, vector<int>&out) {
		for (auto x : v1) {
			for (auto x2 : rg[x])
				if (visit[x2] == 0) {
				s[x2] = 1, v2.push_back(x2), visit[x2] = 1;
			}
		}
		v1.clear();
		for (auto x : v2) {
			for (auto x2 : rg[x])if (visit[x2] == 0) {
				if (--out[x2] == 0)s[x2] = -1, v1.push_back(x2), visit[x2] = 1;
			}
		}
		v2.clear();
	}
};

力扣 913. 猫和老鼠

两位玩家分别扮演猫和老鼠,在一张 无向 图上进行游戏,两人轮流行动。

图的形式是:graph[a] 是一个列表,由满足 ab 是图中的一条边的所有节点 b 组成。

老鼠从节点 1 开始,第一个出发;猫从节点 2 开始,第二个出发。在节点 0 处有一个洞。

在每个玩家的行动中,他们 必须 沿着图中与所在当前位置连通的一条边移动。例如,如果老鼠在节点 1 ,那么它必须移动到 graph[1] 中的任一节点。

此外,猫无法移动到洞中(节点 0)。

然后,游戏在出现以下三种情形之一时结束:

  • 如果猫和老鼠出现在同一个节点,猫获胜。
  • 如果老鼠到达洞中,老鼠获胜。
  • 如果某一位置重复出现(即,玩家的位置和移动顺序都与上一次行动相同),游戏平局。

给你一张图 graph ,并假设两位玩家都都以最佳状态参与游戏:

  • 如果老鼠获胜,则返回 1
  • 如果猫获胜,则返回 2
  • 如果平局,则返回 0 。

 

示例 1:

输入:graph = [[2,5],[3],[0,4,5],[1,4,5],[2,3],[0,2,3]]
输出:0

示例 2:

输入:graph = [[1,3],[0],[3],[0,2]]
输出:1

提示:

  • 3 <= graph.length <= 50
  • 1 <= graph[i].length < graph.length
  • 0 <= graph[i][j] < graph.length
  • graph[i][j] != i
  • graph[i] 互不相同
  • 猫和老鼠在游戏中总是可以移动
class Solution {
public:
	int catMouseGame(vector<vector<int>>& graph) {
		map<int, map<int, map<int, int>>>mn;
		map<int, int>s;
		int id = 0;
		for (int m = 0; m < graph.size(); m++) {
			for (int c = 0; c < graph.size(); c++) {
				for (int t = 0; t <= 1; t++) {
					mn[m][c][t] = id;
					if (m == 0 || c == 0)s[id] = (t ? -1 : 1);
					else if (m == c)s[id] = (t ? 1 : -1);
					id++;
				}
			}
		}
		vector<int>out(id);
		map<int, vector<int>> rg;
		for (int m = 0; m < graph.size(); m++) {
			for (int c = 0; c < graph.size(); c++) {
				for (auto m2 : graph[m])out[mn[m][c][0]]++, rg[mn[m2][c][1]].push_back(mn[m][c][0]);
				for (auto c2 : graph[c])out[mn[m][c][1]]++, rg[mn[m][c2][0]].push_back(mn[m][c][1]);
			}
		}
		SG2::solve(out, rg, s);
		int ans = s[mn[1][2][0]];
		return ans == -1 ? 2 : ans;
	}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/992579.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【List篇】使用Arrays.asList生成的List集合,操作add方法报错

早上到公司&#xff0c;刚到工位&#xff0c;测试同事就跑来说"功能不行了&#xff0c;报服务器异常了&#xff0c;咋回事";我一脸蒙&#xff0c;早饭都顾不上吃&#xff0c;要来了测试账号复现了一下&#xff0c;然后仔细观察测试服务器日志&#xff0c;发现报了一个…

【C语言】错题本(3)

题目: 为了方便分析,我们将题目代码贴在下面: int main() {unsigned char puc[4];struct tagPIM{unsigned char ucPim1;unsigned char ucData0 : 1;unsigned char ucData1 : 2;unsigned char ucData2 : 3;}*pstPimData;pstPimData (struct tagPIM*)puc;memset(puc,0,4);pstPi…

【计算机网络】HTTP(上)

文章目录 1.HTTP概念2. URLurlencode 和 urldecode转义规则 3. HTTP的宏观理解HTTP的请求HTTP的响应 4. 见一见HTTP请求和响应请求报头 1. 模拟一个简单的响应response响应报头 2. 从路径中获取内容ReadFile函数的实现 3.不同资源进行区分反序列化的实现ReadOneLine函数的实现P…

VUE3+JAVA商城源码小程序APP商城

三勾小程序商城基于springbootelement-uiuniapp打造的面向开发的小程序商城&#xff0c;方便二次开发或直接使用&#xff0c;可发布到多端&#xff0c;包括微信小程序、微信公众号、QQ小程序、支付宝小程序、字节跳动小程序、百度小程序、android端、ios端。 软件架构 后端&a…

多维时序 | MATLAB实现GWO-LSTM灰狼算法优化长短期记忆神经网络的多变量时间序列预测

多维时序 | MATLAB实现GWO-LSTM灰狼算法优化长短期记忆神经网络的多变量时间序列预测 目录 多维时序 | MATLAB实现GWO-LSTM灰狼算法优化长短期记忆神经网络的多变量时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现GWO-LSTM灰狼算法优化长短期记忆神…

深圳企业制作宣传片群体定位的重要性

相信众多企业都拍了自己公司的宣传片&#xff0c;尤其是在互联网高度发达的今天&#xff0c;宣传片可以说成为了我们企业对外宣传最主要的方式。看着企业多样式的宣传片种类&#xff0c;我们该如何评价一部宣传片的好坏呢&#xff1f;要知道宣传片的好坏是一个相对主观的问题&a…

Python多进程共享变量实现方法

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 话不多说&#xff0c;直接开搞&#xff0c;如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 一、错误的实现方式 最初以为是没添加global声明导致修改未生效&#xff0c;但实际操作发现global方式在多进程中也只能读…

【ccf-csp题解】第3次csp认证-第三题-集合竞价-枚举

题目描述 思路讲解 本题数据量较小&#xff0c;所以只需要让时间复杂度控制在O&#xff08;n^2&#xff09;就可以满分通过&#xff0c;难度较低 可以用结构体数组事先存下每一个记录的信息&#xff0c;结构体如下&#xff1a; 其中bool值del可以表示这份记录是否已经被删 如…

CSP 202212-1 现值计算

答题 主要就是 #include<iostream>using namespace std;float calculate(float x,float i,int n){float rate1i,ratio1;while(n--){ratioratio*rate;}ratio1/ratio;return x*ratio; }int main() {int n;float rate,x,result0;cin>>n>>rate;for(int i0;i<…

springboot整合mybatisPlus全技巧(2-常用开发技巧:通用字段插入)

本系列专题基于 springboot 整合 mybatisPlus 的各种文章早已烂大街的背景下&#xff0c;根据 整合过程&#xff0c;MP开发中的常见技巧&#xff0c;MP开发中遇到的各种坑 三个方面&#xff0c;来对这一专题做一个全面且实用的总结&#xff0c;基本上只要你吃透这篇文章&#x…

骨传导耳机十大品牌有哪个,骨传导耳机十大品牌排行榜分享

在这个信息爆炸的时代&#xff0c;确实很容易在市场上找到各种各样的骨传导耳机品牌和型号&#xff0c;对于没有相关经验的消费者来说&#xff0c;他们很难判断哪些产品是真正值得信赖的&#xff0c;哪些可能有质量问题或者不适合自己的需求&#xff0c;现如今骨传导耳机的市场…

长胜证券:缩量情绪冰点再现 震荡孕育中期机会

长胜证券指出&#xff0c;近期商场的调整&#xff0c;主要原因来自北向资金扰动和商场牛熊转化期间的信心不足。随着稳增加方针的密布出台&#xff0c;经济修复进程有望加快&#xff0c;增量资金或重回抓反弹阶段&#xff0c;重视量能同步放大水平。一起注册制全面落地后&#…

RLAIF:一个不依赖人工的RLHF替代方案

深度学习自然语言处理 原创作者&#xff1a;Winnie LLM可以标记人类偏好数据&#xff0c;用于强化学习吗&#xff1f;尽管之前有一些类似的研究&#xff0c;但从没有人系统地对比RLHF和RLAIF的性能。今天&#xff0c;我们为大家带来一项Google最新的研究&#xff0c;来看看LLM是…

巧用抽象类与接口,打造高效Java程序(下)

White graces&#xff1a;个人主页 &#x1f649;专栏推荐:Java入门知识&#x1f649; &#x1f649; 内容推荐:巧用抽象类与接口&#xff0c;打造高效Java程序(上)&#x1f649; &#x1f439;今日诗词:登锋履刃&#xff0c;何妨马革裹尸&#x1f439; ⛳️点赞 ☀️收藏⭐️…

护网行动为什么给的钱那么多

因为护网行动是国家应对网络安全问题所做的重要布局之一。 随着大数据、物联网、云计算的快速发展&#xff0c;愈演愈烈的网络攻击已经成为国家安全的新挑战。国家关键信息基础设施可能时刻受到来自网络攻击的威胁。网络安全的态势之严峻&#xff0c;迫切需要我们在网络安全领…

C++11新特性⑤ | 仿函数与lambda表达式

目录 1、引言 2、仿函数 3、lambda表达式 3.1、lambda表达式的一般形式 3.2、返回类型说明 3.3、捕获列表的规则 3.4、可以捕获哪些变量 3.5、lambda表达式给编程带来的便利 VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&a…

Qt QTreeWidge解决setItemWidget后,导致复选框失效

一、问题&#xff1a; QTreeWidget某一项加上itemWidget后&#xff0c;导致复选框失效问题 二、解决方法 将要加上的widget控件加到该项的后续的列&#xff0c;即控件跟复选框不同一列 三、具体代码 QTreeWidget* treeW new QTreeWidget; treeW->setColumnCount(2); /…

2023最全的性能测试种类介绍,这6个种类特别重要!

系统的性能是一个很大的概念&#xff0c;覆盖面非常广泛&#xff0c;包括执行效率、资源占用、系统稳定性、安全性、兼容性、可靠性、可扩展性等&#xff0c;性能测试就是描述测试对象与性能相关的特征并对其进行评价而实施的一类测试。 性能测试是一个统称&#xff0c;它其实包…

微信小程序自动化测试pytest版工具使用方法

-mini https://github.com/zx490336534/pytest-mini 微信小程序自动化测试pytest插件/工具 基于MiniTest进行pytest改造 使用方法 准备测试小程序 根据miniprogram-demo项目介绍运行一次项目 成功运行后关闭 安装&更新 pip install pytest-mini --upgrade引入插件…

5、Nginx 配置实例-负载均衡

文章目录 5、Nginx 配置实例-负载均衡5.1 实现效果5.2 准备工作5.3 实验代码5.3.1、轮询&#xff08;默认&#xff09;5.3.2、weight5.3.3、ip_hash5.3.4、fair&#xff08;第三方&#xff09; 【尚硅谷】尚硅谷Nginx教程由浅入深 志不强者智不达&#xff1b;言不信者行不果。 …