2016多校6 hdu5795 博弈 sg函数


A Simple Nim

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


Problem Description
Two players take turns picking candies from n heaps,the player who picks the last one will win the game.On each turn they can pick any number of candies which come from the same heap(picking no candy is not allowed).To make the game more interesting,players can separate one heap into three smaller heaps(no empty heaps)instead of the picking operation.Please find out which player will win the game if each of them never make mistakes.
 

Input
Intput contains multiple test cases. The first line is an integer  1T100 , the number of test cases. Each case begins with an integer n, indicating the number of the heaps, the next line contains N integers  s[0],s[1],....,s[n1] , representing heaps with  s[0],s[1],...,s[n1]  objects respectively. (1n106,1s[i]109)
 

Output
For each test case,output a line whick contains either"First player wins."or"Second player wins".
 

Sample Input
      
      
2 2 4 4 3 1 2 4
 

Sample Output
      
      
Second player wins. First player wins.
 

Author
UESTC
 

Source


藉由此题来学习sg(Sprague-Grundy)函数和nim游戏。

题意:有若干堆石子,两人凭借自己火热的取胜心(的意思就是这两个人在比试中不回失误)从堆中取石子,遵循以下游戏规则:

1、每次可一丛一堆石子中取任意数量的石子,但每次只能对一堆进行操作

2、也可以选择不取,将一堆石子分成三堆,三堆石子的数量自己决定

3、取走最后石子的人胜

这个游戏叫做nim游戏,似乎是博弈论中很经典的一个游戏。若想做出这道题,首先要了解sg函数是什么,下面谈谈我对sg函数的理解:

给定一个有向无环图和一个起始顶点上的一枚棋子,两名选手交替的将这枚棋子沿有向边进行移动,无法移动者判负。sg函数就能描述这个状态

结合本题来讲,当sg函数等于0时,就说明没有下一步操作了,则此时选手状态输。那么怎么计算sg函数的值呢?当只有一堆石子,我们不对它进行分堆操作的时候,它的sg函数就等于它自身的值。
但题目没有那么简单,还能进行分堆操作,对于多堆的石子的sg函数,等于各个堆的sg值作 ^ 运算(这啥玩意),那各个堆的sg函数怎么算呢,。。就是sg(x) =mex{sg(y) : y ∈ F(x)},意思是在非负整数集中将x的所有后继点的sg值全部去掉,然后集合里还剩下的最小非负整数就是x的sg值,比如某数的所有后继点的sg值为{1,2,4},那么这个数的sg值就为0
题目中给的数非常大,直接对每个输入求sg值似乎是不可能的,所以要根据这个游戏的规则打一打每个数sg值表,看看有没有规律可循。那么开撸sg函数吧,最开始我们只知道0、1的sg值,但很快就可以递推出所有的了!

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <stack>
using namespace std;
const int maxn = 105;
int sg[maxn];
bool vis[maxn];
int main(){
    int i,j,k;
    sg[0]=0;
    for(i=1;i<=100;i++){
        memset(vis, 0, sizeof(vis));
        for(j=0;j<i;j++){
            vis[sg[j]]=true;
        }
        for(j=1;j<i;j++){
            for(k=1;k<i-j;k++){
                vis[sg[j]^sg[k]^sg[i-k-j]]=true;
            }
        }
        for(j=1;vis[j];j++);
        sg[i]=j;
    }
    for(i=0;i<=100;i++){
        cout<<i<<" : "<<sg[i]<<endl;
    }
    return 0;
}
打了个0~100的sg值


根据这个sg值表,我们可以发现8的倍数的数的sg值和他前一位数的sg值是交换的,既然找到规律题目就好做了。将题目输入的多个堆sg的值作 ^ 运算就行了

下面代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <stack>
using namespace std;
const int maxn = 1e6;
int save[maxn+5];
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        int n,i;
        scanf("%d",&n);
        for(i=1;i<=n;i++){
            scanf("%d",&save[i]);
            if(save[i]%8==0){
                save[i] -= 1;
            }else if((save[i]+1)%8==0){
                save[i] += 1;
            }
        }
        int judge=save[1];
        for(i=2;i<=n;i++){
            judge=judge^save[i];
        }
        if(judge==0){
            printf("Second player wins.\n");
        }else{
            printf("First player wins.\n");
        }
    }

    return 0;
}

需要注意的是判断一个数要判断它是不是8的倍数再判断它加一是不是8的倍数,这两个属性只能取其一


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值