HDU 5724 Chess(SG博弈 + 状压)

Chess

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 1673    Accepted Submission(s): 723


Problem Description
Alice and Bob are playing a special chess game on an n × 20 chessboard. There are several chesses on the chessboard. They can move one chess in one turn. If there are no other chesses on the right adjacent block of the moved chess, move the chess to its right adjacent block. Otherwise, skip over these chesses and move to the right adjacent block of them. Two chesses can’t be placed at one block and no chess can be placed out of the chessboard. When someone can’t move any chess during his/her turn, he/she will lose the game. Alice always take the first turn. Both Alice and Bob will play the game with the best strategy. Alice wants to know if she can win the game.
 

Input
Multiple test cases.

The first line contains an integer  T(T100) , indicates the number of test cases.

For each test case, the first line contains a single integer  n(n1000) , the number of lines of chessboard.

Then  n  lines, the first integer of ith line is  m(m20) , indicates the number of chesses on the ith line of the chessboard. Then m integers  pj(1pj20) followed, the position of each chess.
 

Output
For each test case, output one line of “YES” if Alice can win the game, “NO” otherwise.
 

Sample Input
  
  
2 1 2 19 20 2 1 19 1 18
 

Sample Output
  
  
NO YES
 

Author
HIT
 

Source
 

题意:有一个n行20列的棋盘,棋盘上分布着一些棋子,A、B两人轮流下棋,A先手,每次操作可以将某个棋子放到自己右边的第一个空位(也就是说右边如果已经有子,可以跳过它,没有就右移一步),但最多20列,绝对不能超过棋盘,无棋可走的输。

此题首先要理解SG函数,然后用状压进行处理即可(大部分人的代码大同小异,希望读者能够理解其中的变化)
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int MAXN = 1e2 + 5;
const int MAXM = (1 << 21);
int SG[MAXM], Hash[MAXN];
void init(){
    memset(SG, 0, sizeof(SG));
    for(int i = 0;i <= (1 << 20) - 1;i ++){
        memset(Hash, 0, sizeof(Hash));
        for(int j = 0;j < 20;j ++){
            if(i & (1 << j)){
                for(int k = j - 1;k >= 0 ;k --){
                    if(i & (1 << k)) continue;
                    Hash[SG[i ^ (1 << j) ^ (1 << k)]] = 1;//可以用i - (1 << j) + (1 << k),异或的结果是一样的
                    break;//此处为了处理只向右移动一位
                }
            }
        }
        for(int j = 0;j < 20;j ++){
            if(!Hash[j]){
                 SG[i] = j;
                 break;
            }
        }
    }
}
int T, n, m;
int main(){
    init();
    scanf("%d", &T);
    while(T --){
        scanf("%d", &n);
        int sum = 0, x;
        for(int i = 0;i < n;i ++){
            scanf("%d", &m);
            int o = 0;
            while(m --){
                scanf("%d", &x);
                o |= (1 << (20 - x));
            }
            sum ^= SG[o];
        }
        if(sum){
            printf("YES\n");
        }
        else{
            printf("NO\n");
        }
    }
    return 0;
}

总结:直到做了这道多校赛的题后,自己才略微了解了SG函数的一些用法和它的原理,曾想自己去下了杭电的PPT去学习博弈,然后再SG函数在那里懵逼一天,虽然其中偶尔想想心中喜欢的女神外,时间基本全扣在上面,现在看来是值得的,整个SG函数基本已经在脑海中形成一种体系,可能还有些模糊,但是经过以后不断的练习,这种模糊感自然而然就会消失掉,而对于多校赛这道非常经典的SG函数加上状态压缩处理的题目,稍微对SG函数做一下自己所理解的一些看法:
1.Hash数组的用法和数组需要开多大
Hash数组在这里本质就是标记当前节点的后续节点有哪些已经出现了,正如SG函数原本定义的那样,每一个SG值表示的是当前后续节点中的最小自然数的值,所以我们只要想清楚了,一个节点最多有多少个后续节点就可以为Hash开多大的数组了。
对于这道题目,一个棋子在20列中移动,撑死也只有20,所以Hash数组开的大小只要比20大一点点就可以了。这里一定要记住的是Hash中的数组标记即Hash[x]中的x和原来的题目其实是没有多大关系的,他表示的你在进行SG处理的时候,针对每一个后续节点序号的标记(每一个SG值节点的后续节点理论上都要从0标记到最后,然后有些后续节点已经在前面一些节点给标记了,所以不需要从标记,但是有些节点还是必须得从0标记)。
这里的x可能个人讲的不是非常清楚,这里引用SG函数的一些定义,希望可以更好的理解(令N = {0, 1, 2, 3, ...} 为自然数的集合。Sprague-Grundy 函数给游戏中的每个状态分配了一个自然数。结点v的Grundy值等于没有在v的后继的Grundy值中出现的最小自然数)

2.SG数组的函数
SG数组,顾名思义英文名走起:Sprague-Grundy ,它存取的值即为一个有向无环图的g(x)=mex{ g(y) |y是x的后继节点}。这里不多解释,SG最基本的定义了,个人理解还是非常正常的。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值