Light OJ-1344 Aladdin and the Game of Bracelets DP(记忆化搜索) + SG函数 博弈

Light OJ-1344 Aladdin and the Game of Bracelets

题目描述

It’s said that Aladdin had to solve seven mysteries before getting the Magical Lamp which summons a powerful Genie. Here we are concerned about the fourth mystery.


In the cave, Aladdin was moving forward after passing the pathway of magical stones. He found a door and he was about to open the door, but suddenly a Genie appeared and he addressed himself as the guardian of the door. He challenged Aladdin to play a game and promised that he would let Aladdin go through the door, if Aladdin can defeat the Genie. Aladdin defeated the Genie and continued his journey. However, let’s concentrate on the game.


The game was called ‘Game of Bracelets’. A bracelet is a linear chain of some pearls of various weights. The rules of the game are:

1)There are n bracelets.
2)Players alternate turns.
3)In each turn, a player has to choose a pearl from any bracelet. Then all the pearls from that bracelet, that were not lighter than the pearl, will be removed. It may create some smaller bracelets or the bracelet will be removed if no pearl is left in the bracelet.
4)The player, who cannot take a pearl in his turn, loses.


For example, two bracelets are: 5-1-7-2-4-5-3 and 2-1-5-3, here the integers denote the weights of the pearls. Suppose a player has chosen the first pearl (weight 5) from the first bracelet. Then all the pearls that are not lighter than 5, will be removed (from first bracelet). So, 5-1-7-2-4-5-3, the red ones will be removed and thus from this bracelet, three new bracelets will be formed, 1, 2-4 and 3. So, in the next turn the other player will have four bracelets: 1, 2-4, 3 and 2-1-5-3. Now if a player chooses the only pearl (weight 3) from the third bracelet, then the bracelet will be removed.

Now you are given the information of the bracelets. Assume that Aladdin plays first, and Aladdin and the Genie both play optimally. Your task is to find the winner.

样例输入

4
2
7 5 1 7 2 4 5 3
4 2 1 5 4
2
2 5 2
2 5 2
1
5 5 2 5 2 5
3
5 5 2 5 2 5
5 7 2 7 3 2
4 5 1 5 4

样例输出

Case 1: Aladdin
(2 5)
Case 2: Genie
Case 3: Aladdin
(1 2)(1 5)
Case 4: Aladdin
(2 7)(3 1)(3 5)

题意

有 n 个手镯,每个手镯上都有很多带有权值的珍珠。Aladdin 和 Genie 轮流操作,在其中一个手镯上选择一颗珍珠,然后被选择的这颗珍珠和这个手镯上权值大于或等于他的珍珠都会被删除。这样子一次操作后,这个手镯就会变成很多小手镯,或者如果手镯上没有珍珠了,手镯就会被删除。如果最后谁不能操作,谁就输了。最后输出第一步可以必胜的操作 “(选择的手镯,选择的权值)”。

题解

解题主要分为两部分,一是解决输赢,二是解决必胜的取珍珠问题。

第一部分

对于第一部分,由于每个手镯都是独立互不干扰的,所以我们可以考虑求出每个手镯的 SG 值,然后异或起来,如果最后异或的结果不为 0,则先手必胜,否则后手胜。
如此一来,难点变成了求 SG 值。由于每次输入的权值不同,会导致分割节点的不同,所以我们如果在输入权值之前就枚举所有分割情况,复杂度太高。所以我们选择在输入权值后,枚举手镯上的权值,进行分割,然后记忆化搜索。最后如果手镯上的珍珠有 0 个,必输,此时 SG = 0。如果有一个,必胜 SG = 1;
我们考虑用 S G [ i ] [ j ] [ k ] SG[i][j][k] SG[i][j][k] 来存储第 i i i 个手镯,区间为 [ j , k ] [j, k] [j,k] 的 SG 值进行记忆化搜索,也就是DP,如果不了解记忆化搜索,可以去学习一下。
记忆化搜索 SG 值框架:

memset(SG, -1, sizeof SG);
int find(int num, int l, int r) {//对第num条项链,区间为 [l,r] 进行搜索
	if (SG[num][l][r] != -1) return SG[num][l][r]; //如果已经被搜索过,直接返回
	if (l == r) return SG[num][l][r] = 1; //如果手镯只有一个珍珠,SG赋值为1,并返回
	if (r < l) return SG[num][l][r] = 0; //如果手镯没有珍珠,必输,返回0
	int now_SG = 0;
	//如果不满足以上条件,则寻找这种情况的SG值。
	return SG[num][l][r] = now_SG;
}

现在的问题只剩下,如果不符合以上情况,应该怎么去找 SG 值,我们可以去枚举这个手镯上所有珍珠,假设去掉这个珍珠,他的SG值是多少。然后获得一个mex_SG的集合,那他的SG就是不属于这个集合的最小非负整数。
我们知道如果一个手镯 5-1-7-2-4-5-3,如果我们选择了 5,那就会删去 5,7,5,然后变成了 1,2-4,3 三个手镯,那此时去掉 5 的情况下的 SG 值就会 1,2-4,3,这三个手镯的异或值。
记忆化搜索SG值:

memset(SG, -1, sizeof SG);
int find(int num, int l, int r) {//对第num条项链,区间为 [l,r] 进行搜索
	if (SG[num][l][r] != -1) return SG[num][l][r]; //如果已经被搜索过,直接返回
	if (l == r) return SG[num][l][r] = 1; //如果手镯只有一个珍珠,SG赋值为1,并返回
	if (r < l) return SG[num][l][r] = 0; //如果手镯没有珍珠,必输,返回0
	int now_SG = 0;
	//定义vis记录mex_SG集合
	bool vis[maxn]; memset(vis, false, sizeof vis);//vis只能在函数体内定义。
	//枚举每一颗珍珠
	for (int i = l; i <= r; i++) {
		int tmp = 0;// 记录当前的情况下的SG值
		int t = 现在这个珍珠的权值;
		for (int k = 0; k < 分成的区间; k++) {
			//遍历所有分割的区间
			tmp ^= find(num, k_l, k_r);//找第Num条手镯,区间为[k_l, k_r]的 SG 值
		}
		vis[tmp] = true;//标记SG值
	}
	//对应mex_SG找到这个区间的SG值
	for (int i = 0; i < maxn; i++) {
		if (vis[i] == false) {
			now_SG = i; break;
		}
	}
	return SG[num][l][r] = now_SG;
}
第二部分

解决第二部分问题的前提是,知道输赢的结果,如果是Genie赢,则跳过。Aladdin赢,则判断。
这个判断我们也是枚举所有的珍珠,看选择这颗珍珠是否必胜,如果必胜,则记录下来。

最后排序,去重,然后输出。

那我们假设我们已经的得到了所有手镯的异或值为 ans (ans > 0)。
当我们枚举第 num 条手镯时,我们就先用 ans = ans ^ SG[num][1][len_num],这个结果是消除了第 num 条手镯的影响,也就是除了第 num 条手镯之外的其他手镯的异或值。
然后我们得到了第 num 条手镯选择了某颗珍珠的 SG 值为 now_SG。
那么我们就将后来的 ans ^ now_SG,如果结果为 0,则说明选择这颗珍珠必胜。
总的来说就是第一部分的 ans ^ SG[num][1][len_num] ^ now_SG。
我们就可以枚举所有的珍珠,如果可以使得最后的异或结果为 0 则加入到答案中,后来排序,去重再输出。

问题: 为什么异或结果为 0 必胜?
因为最后计算出来的结果是选择了这颗珍珠之后的输赢状态。Aladdin 选择了这颗珍珠,则到了 Genie 先手,此时 ans = 0,先手必败,则Genie 必败,因此 Aladdin 必胜。

代码

在代码中,我使用了 a[i][j] 来表示第 i 条手镯第 j 个珍珠的权值,用 a[i][0] 存储第 i 条手镯的长度。
用 node 结构体来存储最后的答案,然后重载了 < 和 == ,用于答案的排序和去重。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 55;
int sg[maxn][maxn][maxn], a[maxn][maxn], n, T, ca;

struct node {
  int a, b;
  node(int c = 0, int d = 0) {a = c, b = d;}
  bool operator < (node &t) const {
    if (a == t.a) return b < t.b;
    return a < t.a;
  }
  bool operator == (node &t) const {
    return (a == t.a && b == t.b);
  }
} res[maxn * maxn];

int findSG(int num, int l, int r) {
  if (sg[num][l][r] != -1) return sg[num][l][r];
  if (r == l) {
    return sg[num][l][r] = 1;
  }
  if (r < l) {
    return sg[num][l][r] = 0;
  }

  bool vis[maxn];
  memset(vis, false, sizeof vis);
  for (int i = l; i <= r; i++) {
    int t = a[num][i], tmp[maxn], cnt = 0, ans = 0;
    //记录比 t 大的 所有分割点
    tmp[cnt++] = l - 1;
    for (int p = l; p <= r; p++) {
      if (a[num][p] >= t) tmp[cnt++] = p;
    }
    tmp[cnt++] = r + 1;

    for (int p = 1; p < cnt; p++) {
      ans ^= findSG(num, tmp[p - 1] + 1, tmp[p] - 1);
    }
    vis[ans] = true;
  }
  for (int i = 0; i < maxn; i++) {
    if (vis[i] == false) {
      sg[num][l][r] = i; break;
    }
  }
  return sg[num][l][r];
}

int main()
{
  scanf("%d", &T);
  while (T--) {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
      scanf("%d", &a[i][0]);
      for (int j = 1; j <= a[i][0]; j++) {
        scanf("%d", &a[i][j]);
      }
    }
    memset(sg, -1, sizeof sg);
    int ans = 0;
    for (int i = 0; i < n; i++) {
      ans ^= findSG(i, 1, a[i][0]);
    }

    printf("Case %d: ", ++ca);
    if (ans) printf("Aladdin\n");
    else printf("Genie\n");

    if (ans) {
      int cnt = 0;
      for (int i = 0; i < n; i++) {
        // 枚举手镯
        for (int j = 1; j <= a[i][0]; j++) {
          // 枚举选中的珍珠
          int ret = ans ^ sg[i][1][a[i][0]];
          int t = a[i][j], tmp[maxn], k = 0;
          //记录比 t 大的 所有分割点
          tmp[k++] = 0;
          for (int p = 1; p <= a[i][0]; p++) {
            if (a[i][p] >= t) tmp[k++] = p;
          }
          tmp[k++] = a[i][0] + 1;
          for (int p = 1; p < k; p++) {
            ret ^= findSG(i, tmp[p - 1] + 1, tmp[p] - 1);
          }
          //与其他相连进行Nim和
          if (!ret) {
            res[cnt++] = node(i, t);
          }
        }
      }
      sort(res, res + cnt);
      cnt = unique(res, res + cnt) - res;
      for (int i = 0; i < cnt; i++) {
        printf("(%d %d)", res[i].a + 1, res[i].b);
      }
      printf("\n");
    }
  }

  return 0;
}
/*
4
2
7 5 1 7 2 4 5 3
4 2 1 5 4
2
2 5 2
2 5 2
1
5 5 2 5 2 5
3
5 5 2 5 2 5
5 7 2 7 3 2
4 5 1 5 4
*/

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看REAdMe.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看REAdMe.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看READme.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
SDUT-OJ(Software Development University of Tsinghua Online Judge)是一个在线编程平台,提供给清华大学软件学院的学生和爱好者练习和解决算法问题的环境,其中包括各种计算机科学题目,包括数据结构、算法、图形等。对于"最小生成树"(Minimum Spanning Tree, MST)问题,它是图论中的经典问题,目标是从一个加权无向图中找到一棵包含所有顶点的树,使得树的所有边的权重之和最小。 在C语言中,最常见的是使用Prim算法或Kruskal算法来求解最小生成树。Prim算法从一个顶点开始,逐步添加与当前生成树相连且权重最小的边,直到所有顶点都被包含;而Kruskal算法则是从小到大对所有边排序,每次选取没有形成环的新边加入到树中。 如果你想了解如何用C语言实现这些算法,这里简单概括一下: - 通常使用优先队列(堆)来存储边和它们的权重,以便快速查找最小值。 - 从任意一个顶点开始,遍历与其相邻的边,若新边不形成环,就更新树,并将新边加入优先队列。 - Kruskal算法: - 先将所有的边按照权重升序排序。 - 创建一个空的最小生成树,然后依次取出排序后的边,如果这条边连接的两个顶点不在同一个连通分量,则将其添加到树中。 如果你需要更详细的代码示例,或者有具体的问题想了解(比如如何处理环、如何实现优先队列等),请告诉我,我会为你提供相应的帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值