HDU5724 Chess(SG函数+状压)
题意:有一个 N ∗ 20 N*20 N∗20 大小的棋盘,上面已经存在一些棋子,Alice 和 Bob 轮流每回合选择一个棋子跳到右边第一个没有被棋子占据的格子中,不能越界,无法进行合法移动者失败,问 A l i c e Alice Alice 先手是否有必胜策略。
范围: N ≤ 1000 N \le 1000 N≤1000
分析:根据数据范围可以知道本题不需要找规律,考虑到每个棋子的转移方式也比较单一,可以尝试 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】感谢观看