HDU5724 Chess(SG函数+状压)

HDU5724 Chess(SG函数+状压)

题面

题意:有一个 N ∗ 20 N*20 N20 大小的棋盘,上面已经存在一些棋子,Alice 和 Bob 轮流每回合选择一个棋子跳到右边第一个没有被棋子占据的格子中,不能越界,无法进行合法移动者失败,问 A l i c e Alice Alice 先手是否有必胜策略。

范围 N ≤ 1000 N \le 1000 N1000

分析:根据数据范围可以知道本题不需要找规律,考虑到每个棋子的转移方式也比较单一,可以尝试 S G SG SG 函数。因为对于某一列的棋子状态可以用二进制来表示,且棋盘的宽度只有 20 20 20,可以使用可以一个数字来状压。

能想清楚这些问题已经解决一大半了,详见代码。

Code

#include <bits/stdc++.h>
#define int long long
#define double long double
using namespace std;

const int MAXN = 1000 + 10;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const double eps = 1e-9;
const double PI = acos(-1.0);

int n;

inline int read()
{
	int s = 0, w = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9')
	{
		if (ch == '-')
			w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9')
		s = s * 10 + ch - '0', ch = getchar();
	return s * w;
}

// 对于一行的棋子状态一共有2^20种,此时二进制的01顺序与棋盘相反
int sg[(1 << 20) + 10];

// 预处理所有情况的sg值
void SG()
{
    // 没有棋子,必败
	sg[0] = 0;
    // 遍历所有情况
	for (int i = 1; i <= (1 << 20); i++)
	{
        // last[i]用来保存棋子i最右侧连续的棋子
        // vis用来计算mex
		int last[30], vis[30];
		memset(vis, 0, sizeof(vis));
        // 处理last数组
		for (int j = 0; j < 20; j++)
		{
			if ((i >> j) & 1)
			{
				if (j == 0)
					last[j] = j;
				else if ((i >> (j - 1)) & 1)
					last[j] = last[j - 1];
				else
					last[j] = j;
			}
		}
        // 更新vis数组
		for (int j = 0; j < 20; j++)
		{
			if ((i >> j) & 1)
			{
				if (last[j] - 1 >= 0)
				{
                    // 二进制中位置j的1挪到位置last[j]-1
					int temp = i - (1 << j) + (1 << (last[j] - 1));
					vis[sg[temp]] = 1;
				}
			}
		}
        // 计算mex
		for (int j = 0; j < 30; j++)
		{
			if (!vis[j])
			{
				sg[i] = j;
				break;
			}
		}
	}
}

signed main()
{
	SG();
	int T = read();
	while (T--)
	{
		n = read();
		int ans = 0;
        // 对每一行的sg值进行异或
		for (int i = 1; i <= n; i++)
		{
			int temp = 0;
			int num = read();
			for (int j = 1; j <= num; j++)
			{
				int pos = read();
                // 注意与棋盘顺序相反
				temp ^= 1 << (20 - pos);
			}
			ans = ans ^ sg[temp];
		}
        // 根据SG定理非0必胜
		if (ans)
		{
			cout << "YES" << endl;
		}
		else
		{
			cout << "NO" << endl;
		}
	}
	return 0;
}

【END】感谢观看

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值