sg函数例题

转自http://blog.csdn.net/u014355480/article/details/47397913
ZOJ - 2083 - Win the Game

题目传送:Win the Game

最近正在慢慢体会博弈里面的SG函数的意义

此题是最简单的SG博弈问题,只需打个表就OK了

AC代码:

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0x7fffffff
using namespace std;

int sg[55];
int vis[55];

void get_sg() {
    sg[0] = 0;
    sg[1] = 0;
    for(int i = 2; i < 55; i ++) {
        memset(vis, 0, sizeof(vis));
        vis[sg[i - 2]] = 1;
        for(int j = 1; j + 2 <= i; j ++) {
            vis[sg[j] ^ sg[i - j - 2]] = 1;
        }
        for(int j = 0;; j ++) {
            if(!vis[j]) {
                sg[i] = j;
                break;
            }
        }
    }
}

int n;

int main() {
    get_sg();
    //for(int i = 0; i < 55; i ++) cout << sg[i] << " ";
    while(scanf("%d", &n) != EOF) {
        int SG = 0, t;
        for(int i = 0; i < n; i ++) {
            scanf("%d", &t);
            SG ^= sg[t];
        }
        if(SG == 0) {
            printf("No\n");
        }
        else printf("Yes\n");
    }
    return 0;
}





ZOJ - 2507 - Let’s play a game

题目传送:Let’s play a game

AC代码:

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0x7fffffff
using namespace std;

int T;

int main() {
    scanf("%d", &T);
    while(T --) {
        int n, t, cnt = 0;;
        int SG = 0;
        scanf("%d", &n);
        for(int i = 0; i < n; i ++) {
            scanf("%d", &t);
            SG ^= t;
            if(t > 1) cnt ++;
        }
        if(cnt == 0) {
            if(SG == 1) {
                printf("2\n");
            }
            else printf("1\n");
        }
        else {
            if(SG) {
                printf("1\n");
            }
            else printf("2\n");
        }
    }
    return 0;
}




ZOJ - 3529 - A Game Between Alice and Bob

题目传送:A Game Between Alice and Bob

和之前UVA那个题类似,也是打表筛出素因子个数,然后套SG即可

AC代码:

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0x7fffffff
using namespace std;

const int maxn = 5000005;

int n;

int a[100005];

int chu[maxn];
int to[maxn];

void init() {
    to[1] = 0;
    for(int i = 2; i < maxn; i ++) chu[i] = i;

    for(int i = 2; i < maxn; i ++) {
        if(to[i]) continue;
        to[i] ++;
        for(int j = 2 * i; j < maxn; j += i) {
            while(chu[j] % i == 0) {
                to[j] ++;
                chu[j] /= i;
            }
        }
    }
}

int main() {
    init();
    //for(int i = 1; i < 10; i ++) cout << to[i] << " ";
    int cas = 1;
    while(scanf("%d", &n) != EOF) {
        int SG = 0, t;
        for(int i = 0; i < n; i ++) {
            scanf("%d", &t);
            a[i] = to[t];       
            SG ^= a[i];
        }
        if(SG == 0) {
            printf("Test #%d: Bob\n", cas);
        }
        else {
            printf("Test #%d: Alice", cas);
            for(int i = 0; i < n; i ++) {
                if(a[i] > (a[i] ^ SG)) {
                    printf(" %d\n", i + 1);
                    break;
                }
            }
        }

        cas ++;
    }
    return 0;
}




ZOJ - 3513 - Human or Pig

题目传送:Human or Pig

注意题目要输出的P点和H点的含义。

含义是:

    P点,此时的状态为猪,且随便怎么走都能够满足条件,即落入水中时的状态为猪,然后可以永远变为人类
    H点,此时的状态为人类,且只需要下一状态有一个为猪即可,因为状态为猪时随便走都可以满足条件

AC代码:

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0x7fffffff
using namespace std;

const int maxn = 40005;
int mp[maxn];//由于X*Y<=40000,但是这里开40000*40000的数组又太大了,所以这里我们应该用一维数组来模拟二维数组

int X, Y;//行数以及列数

int& get_pos(int x, int y) {//获取一维数组上对应的二维数组上的那个值
    return mp[(x - 1) * Y + y];
}

//这里需要注意P点和H点的含义,即
//H点代表此时只能是人类,才能确保落入水中时的状态是猪
//P点代表此时是猪,也能确保落入水中时的状态是猪,这一步只需要保证他的后继状态都是H或者直接掉入水中即可
//
//通俗一点的来说就是随便乱走都可以满足条件的就是P点,然后可以根据后继状态有P来确保满足条件的就是H点

char get_char(int x, int y) {//获取一次的
    int &ret = get_pos(x, y);
    ret = 'P';//先初始化为P点,P点代表此时是猪,因为猪是没思想的,可以随便走,所以初始化为这个

    //只要当前状态的后继状态出现了P点就可以转化成H点,因为H点代表人类,人类是有思想的,可以指定选择走到P点,而走到P点后随意走都可以满足条件
    //如果当前状态的后继状态出现了P点,而又没有转化,则当前不能确保能满足条件
    for(int k = 1; y > k * x; k ++) {
        if(get_pos(x, y - k * x) == 'P') {
            ret = 'H';
            break;
        }
    }

    for(int k = 1; x > k * y; k ++) {
        if(get_pos(x - k * y, y) == 'P') {
            ret = 'H';
            break;
        }
    }

    return ret;
}

int main() {
    int cas = 1;
    while(scanf("%d %d", &X, &Y) != EOF) {
        printf("Case #%d:\n", cas ++);
        for(int i = 1; i <= X; i ++) {
            for(int j = 1; j <= Y; j ++) {
                putchar(get_char(i, j));
            }
            puts("");
        }
    }
    return 0;
}

转自http://blog.csdn.net/thearcticocean/article/details/51240388

hdu 3032 Nim or not Nim?

http://acm.hdu.edu.cn/showproblem.php?pid=3032
大意:和普通的nim游戏相比,这里新添加了一种操作,可以把数字分成更小的两份。
分析:
sg[0]=0
当i=1,取走1后剩下0,mex[1]=1. sg[1]=1
当i=2时,可以取12,剩下10,sg[1]=1,sg[0]=0,分解成1+1,sg[1]^sg[1]=0,即分解相同的两部分和取走全部的效果是一样的。mex(2)=2 sg[2]=2
当i=3时,可以取123,剩下210,sg[2]=2, sg[1]=1, sg[0]=0,分解: sg[1]^sg[2]=3,所以mex(3)=4. sg[3]=4
当i=4时,取:1234,剩下:sg[3]=4, sg[2]=2, sg[1]=1, sg[0]=0, 分解: sg[1]^sg[3]=5 mex(5)=3. sg[4]=3.

打表:

int sg[N];
bool vis[N];
void init(int n){
    int i,j;
    for(i=2;i<n;i++){
        memset(vis,0,sizeof(vis));
        for(j=1;j<=n;j++) vis[sg[i-j]]=1; // 取
        for(j=1;j+j<=n;j++) vis[sg[j]^sg[i-j]]=1;  // 分解
        j=0;
        while(vis[j]) j++;
        sg[i]=j;
    }
}
/*
1: 1
2: 2
3: 4
4: 3
5: 5
6: 6
7: 8
8: 7
9: 9
10: 10
11: 12
12: 11
13: 13
14: 14
15: 16
16: 15
17: 17
18: 18
19: 20
20: 19
21: 21
22: 22
23: 24
24: 23
*/
1



得到规律:
sg[4n+3]=4n+4
sg[4n]=4n-1
sg[i]=i

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

int main()
{
    //freopen("cin.txt","r",stdin);
    int n,t;
    cin>>t;
    while(t--){
        scanf("%d",&n);
        int a,ans=0;
        for(int i=0;i<n;i++){
            scanf("%d",&a);
            if((a&3)==3) {  // a%4==3
                ans=ans^(a+1);
            }
            else if((a&3)==0){  // a%4==0
                ans=ans^(a-1);
            }
            else ans=ans^a;
        }
        if(ans) puts("Alice");
        else puts("Bob");
    }
    return 0;
}



acdream 1112 Alice and Bob

http://acdream.info/problem?pid=1112
大意:给出一堆数字,每一个数字可以被不是它本身的约数(包括1)替代,或者分解成两个约数。比如6可以变成:2, 3, (2,3)
分析:一开始使用直接分解计算的方法果断超时。事实上这个问题完全可以从另一个角度看,由算术基本定理可知, n=ap11ap22⋯apkk

那么n变小的过程就是素因子减少的过程。由此问题变成n的素因子个数变少的sg问题。
对于范围是5e6的未知数,可以猜想他的素因子的个数一定小于301<<30一定大于它)
由此我们可以算出每一个数字的素因子的个数,然后计算出这30个数字的sg函数值(预处理)。
至于如何计算出每一个数字的素因子的个数,可以使用前缀和的思想,sum[n]=sum[n/p]+1 (假设p是n的最小素因子)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
const int N=5e6+10;
int sg[N],minp[N],sum[N];
int maxs,cnt;
vector<int> prim;
bool vis[N];
void get_minp(){
    minp[0]=minp[1]=0;
    for(int i=2;i<N;i++) minp[i]=i;
    for(int i=2;i<N;i++){
        if(!vis[i]){ prim.push_back(i); cnt++; }
        for(int j=0;j<cnt && prim[j]*i<N;j++){
            int t=i*prim[j];
            vis[t]=1;
            minp[t]=min(minp[t],prim[j]);
            if(i%prim[j]==0) break;
        }
    }
}
void get_sg(){
    sg[0]=0;
    sum[0]=sum[1]=0;
    maxs=0;
    int i,j;
    for(i=2;i<N;i++){
        sum[i]=sum[i/minp[i]]+1;
        maxs=max(maxs,sum[i]);
    }
    for(i=1;i<=maxs;i++){
        for(j=0;j<=i+1;j++) vis[j]=0;  // important!!!
        for(j=1;j+j<=i;j++){
            int t1=sg[j], t2=sg[i-j];
            vis[t1]=vis[t2]=vis[t1^t2]=1;
        }
        vis[0]=1;  // 用1代替原数字  
        j=0;
        while(vis[j]) j++;
        sg[i]=j;
    }
}
int main()
{
    //freopen("cin.txt","r",stdin);
    get_minp();
    get_sg();
    int n,a;
    while(~scanf("%d",&n)){
        int ans=0;
        for(int i=0;i<n;i++){
            scanf("%d",&a);
            ans=ans^sg[sum[a]];
        }
        if(ans) puts("Alice");
        else puts("Bob");
    }
    return 0;
}



Treblecross

此题类似于: uva 10561
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1502
大意:对一个一维的棋带,两人玩游戏,每一个人将其中的 ‘.’ 变成 ‘x’ ,谁最先满足走一步棋后又3个连续的x排列在一起谁就胜利。
分析:《算法竞赛 训练指南》 p139
对于:_ _ x的情况再走一步棋必败,所以得到 _ _ x _ _ 的影响领域,
进一步分析:
这里写图片描述
一个x的影响:sg(x)=mex(sg(x-3), sg(x-4), sg(x-5), sg(1)^sg(x-6), sg(2)^(sg(x-7)……)
所以枚举每一个点,然后计算sg异或结果,得到能够放棋子的位置
初始值:
sg(0)=0
sg(1)=sg(2)=sg(3)=1

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=210,inf=0x3f3f3f3f;
char str[N];
int sg[N];
int mex(int n){  // 递归求解sg[n]
    if(sg[n]!=-1) return sg[n];
    if(n==0) return sg[0]=0;
    bool tag[N];
    memset(tag,0,sizeof(tag));
    /*for(int i=3;i<6&&i<=n;i++) tag[mex(n-i)]=1;
    for(int i=6;i<=n;i++){
        int g=mex(n-i)^mex(i-5);
        tag[g]=1;
    }*/
    for(int i=1;i<=n;i++){
        int g=mex(max(0,i-3))^mex(max(0,n-i-2));
        tag[g]=1;
    }
    for(int i=0;i<N;i++)
        if(tag[i]==0) return sg[n]=i;
}
bool win(int n,int dex){ // 检测 XXX 区域
    char ch=str[dex];
    str[dex]='X';
    for(int i=0;i+2<n;i++){
        if(str[i]=='X' && str[i+1]=='X' && str[i+2]=='X') {
            str[dex]=ch;
            return true;
        }
    }
    str[dex]=ch;
    return false;
}
bool will_win(int n,int dex){  // 检测 . 区域
    char ch=str[dex];
    str[dex]='X';
    for(int i=0;i<N;i++){  // 第二种情况里的假设可能造成第一种情况成立,
        if(str[i]=='.'){   //但它不属于第二种情况
            if(win(n,i)) {
                str[dex]=ch;
                return false;
            }
        }
    }

    int ans=0,temp=0;
    for(int i=0;i<n;i++){
        if(str[i]=='X' || (i-1>=0&&str[i-1]=='X') || (i-2>=0&&str[i-2]=='X')
           || (i+1<n&&str[i+1]=='X') || (i+2<n&&str[i+2]=='X')){
            ans=ans^mex(temp);
            temp=0;
        }
        else temp++;
    }
    ans=ans^mex(temp);
    str[dex]=ch;
    return ans==0;
}
int main()
{
    //freopen("cin.txt","r",stdin);
    memset(sg,-1,sizeof(sg));
    //sg[0]=0;
    //sg[1]=sg[2]=sg[3]=1;
    int t,ca=1;
    cin>>t;
    while(t--){
        scanf("%s",str);
        int n=strlen(str);
        int ans[N],top=0;
        for(int i=0;i<n;i++){
            if(str[i]=='X') continue;
            if(win(n,i) || will_win(n,i)) ans[top++]=i+1;
        }
        if(top) {
            printf("Case %d: ",ca++);
            for(int i=0;i<top-1;i++) printf("%d ",ans[i]);
            printf("%d\n",ans[top-1]);
        }
        else printf("Case %d: 0\n",ca++);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值