杭电博弈练习

花了两天写博弈论,16道题差不多了,阶梯尼姆还存在点问题,有机会碰到了再研究。sg函数大概明白了,真就是个暴力?

Calendar Game——HDU - 1079

链接:https://acm.hdu.edu.cn/showproblem.php?pid=1079

大意

给定1900.1.1日到2001.11.4日中的一天,Adam和Eve轮流跳转日期,adam先跳,谁先到2001.11.4日谁赢。跳转日期有两种跳法:1、跳转到当前日期的下一天;2、跳转到下个月的对应日,如不存在则只能选择1跳转。

思路

sg函数标记必胜点,处理日期有点麻烦,处理好了暴力dfs就行。
数据很水,代码没处理年份都能过。。。(已修改

CODE

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+7;
int rq[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
int f[3000][20][50];
int solve(int n,int y,int r)
{
    if(f[n][y][r]!=-1)
    {
        return f[n][y][r];
    }
    if(r<rq[y])
    {
        if(solve(n,y,r+1)==0)
            return f[n][y][r]=1;
    }
    else if(y==2&&((n%4==0&&n%100!=0)||n%400==0)&&r==28)
    {
        if(solve(n,y,r+1)==0)
            return f[n][y][r]=1;
    }
    else if(y==12&&r==31)
    {
        if(solve(n+1,1,1)==0)
            return f[n][y][r]=1;
    }
    else
    {
        if(solve(n,y+1,1)==0)
            return f[n][y][r]=1;
    }
    if((n==2001&&y==10&&r<=4)||(n<2001)||(n==2001&&y<10))
    {
        if(rq[y%12+1]>=r)
        {
            if(solve(n,y+1,r)==0)
                return f[n][y][r]=1;
        }
        else if(y==1&&r==29&&((n%4==0&&n%100!=0)||n%400==0))
        {
            if(solve(n,y+1,r)==0)
                return f[n][y][r]=1;
        }
        else if(y==12)
        {
            if(solve(n+1,1,r)==0)
                return f[n][y][r]=1;
        }
    }
    return f[n][y][r]=0;
}
int main()
{
    ios::sync_with_stdio(0);
    memset(f,-1,sizeof f);
    int t;
    cin>>t;
    f[2001][11][4]=0;
    while(t--)
    {
        int n,y,r;
        cin>>n>>y>>r;
        if(solve(n,y,r)==0)
        {
            cout<<"NO"<<endl;
        }
        else
        {
            cout<<"YES"<<endl;
        }
    }
    return 0;
}

Public Sale——HDU-2149

链接:https://acm.hdu.edu.cn/showproblem.php?pid=2149

大意

物品成本价M,每个人每次加价幅度为1~N,当价格大于或等于成本价时获得胜利。

思路

巴什博弈板题,m%(1+n)== 0时先手必输,否则存在先手获胜拿法,全部输出即可。

CODE

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+7;
int n,m;
int main()
{
    //ios::sync_with_stdio(0);
    while(cin>>m>>n)
    {
        if(m<=n)
        {
            for(int i=m;i<=n;i++)
            {
                if(i==m)    cout<<i;
                else    cout<<" "<<i;
            }
            cout<<'\n';
        }
        else
        {
            if(m%(n+1)==0)
                cout<<"none"<<endl;
            else
            {
                int temp=0;
                for(int i=1;i<=n;i++)
                {
                    if((m-i)%(n+1)==0)
                    {
                        if(!temp)   temp++,cout<<i;
                        else cout<<' '<<i;
                    }
                }
                cout<<'\n';
            }
        }
    }
    return 0;
}

Euclid’s Game——HDU-1525

链接:https://acm.hdu.edu.cn/showproblem.php?pid=1525

大意

给定两个数a,b,两个人每次可用大的值减去小的值的数倍(差不能为负数),最先减到0的获胜。

思路

如果a>=b,且a<2b,那么走法固定(a-b),如果a>2b,那么当前玩家便可以根据后面的固定的走法选择走必赢的一步。

CODE

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
const int N=1e6+7;
int n,m;
int main()
{
    ios::sync_with_stdio(0);
    while(cin>>n>>m)
    {
        if(n==0&&m==0)break;
        if(n<m) swap(n,m);
        if(n%m==0)
        {
            cout<<"Stan wins"<<'\n';continue;
        }
        int temp=1;
        while(1)
        {
            if(m==0||n%m==0||n>=2*m)
                break;
            n-=m;
            swap(n,m);
            temp=!temp;
        }
        if(temp)
            cout<<"Stan wins"<<'\n';
        else
            cout<<"Ollie wins"<<'\n';
    }
    return 0;
}

Being a Good Boy in Spring Festival——HDU-1850

链接:https://acm.hdu.edu.cn/showproblem.php?pid=1850

大意

有m堆扑克牌,每次可以选择一堆并取走其中任意张牌,最后一次取牌的人获胜,问先手想赢的话第一步有几种选择。

思路

尼姆博弈板题,将当前堆数目减少到与其他所有堆数目异或和的异或和为0,则一定胜利,遍历寻找即可。

CODE

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+7;
int n,m;
int a[N];
int main()
{
    ios::sync_with_stdio(0);
    while(cin>>m)
    {
        if(m==0)break;
        ll ans=0;
        for(int i=1;i<=m;i++)
        {
            cin>>a[i];
            ans^=a[i];
        }
        ll cnt=0;
        for(int i=1;i<=m;i++)
        {
            if(a[i]>(ans^a[i]))
            {
                cnt++;
            }
        }
        cout<<cnt<<endl;
    }
    return 0;
}

Play a game——HDU-1564

链接:https://acm.hdu.edu.cn/showproblem.php?pid=1564

大意

给定一个n*n的矩阵,第一个点在最角落,每人每次走一格,走过的格子不能再走,最后不能再走的人判负,输出获胜的人

思路

找规律题,剩余格子为奇数时先手必胜

CODE

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
const int N=1e6+7;
int n,m;

int main()
{
    ios::sync_with_stdio(0);
    while(cin>>n)
    {
        if(n==0)
            break;
        if((n*n-1)%2==0)
            cout<<"ailyanlu"<<endl;
        else
            cout<<"8600"<<endl;
    }
    return 0;
}

取(m堆)石子游戏——HDU-2176

链接:https://acm.hdu.edu.cn/showproblem.php?pid=2176

大意

给定m堆石子数量,每次从一堆石子里面取任意个,最后一个取的人获胜。

思路

尼姆博弈,将当前堆数目减少到与其他所有堆数目异或和的异或和为0,则一定胜利,遍历寻找每堆石子是否满足条件,满足则输出。

CODE

#include<iostream>
#include<cstring>
using namespace std;
int a[1000009];
int main()
{
	int m;
	while(~scanf("%d",&m))
	{
		if(m == 0)
		{
			return 0;
		}
		memset(a,0,sizeof(a));
		for(int i=0;i<m;++i)
		{
			scanf("%d",&a[i]);
		}
		int sum = 0; 
		for(int j = 0;j < m;++j)
		{
			sum = sum^a[j]; 
		} 
		if(sum == 0)
		{
			printf("No\n");continue;
		}
		else{
			
			printf("Yes\n");int u=0;
			for(int j = 0;j < m;++j)
			{
				u = sum;
				u^=a[j];
				if(u<a[j])
				{
					printf("%d %d\n",a[j],u);	
				}                   
		} 
		}
		
	}
	return 0;
 } 

Brave Game——HDU-1846

链接:https://acm.hdu.edu.cn/showproblem.php?pid=1846

大意

n个石子,每次可以取走1~m个,问胜者

思路

巴什博弈,不用脑子直接无脑敲

CODE

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;

int main()
{
    ios::sync_with_stdio(0);
    int t;
    cin>>t;
    while(t--)
    {
        int n,m;
        cin>>n>>m;
        if(n%(m+1))
        {
            cout<<"first"<<endl;
        }
        else
        {
            cout<<"second"<<endl;
        }
    }
    return 0;
}

取石子游戏——HDU-1527

链接:https://acm.hdu.edu.cn/showproblem.php?pid=1527

大意

两堆石子,每次可以从一堆里取出任意石子或从两堆中取出相同数目石子,问胜者。

思路

威佐夫博弈,赞美完美比例!当n>m,如果(m-n)* (int)(((sqrt(5.0)+1)/2)==n则为必输态,否则为必胜态。

CODE

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;

int main()
{
    ios::sync_with_stdio(0);
    int n,m;
    while(cin>>n>>m)
    {
        if(n>m)
            swap(n,m);
        if((int)(((sqrt(5.0)+1)/2)*(m-n))==n)
        {
            cout<<0<<endl;
        }
        else
        {
            cout<<1<<endl;
        }
    }
    return 0;
}

Good Luck in CET-4 Everybody!——HDU-1847

链接:https://acm.hdu.edu.cn/showproblem.php?pid=1847

大意

一共n张牌,每人每次抓牌的个数只能是2的幂次,谁能赢。

思路

sg函数打表发现规律为3的倍数必败

CODE

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;

int main()
{
    ios::sync_with_stdio(0);
    int n,m;
    while(cin>>n)
    {
        if(n%3)
        {
            cout<<"Kiki"<<endl;
        }
        else{
            cout<<"Cici"<<endl;
        }
    }
    return 0;
}

取(2堆)石子游戏——HDU-2177

链接:https://acm.hdu.edu.cn/showproblem.php?pid=2177

大意

两堆石子,每次从一堆拿任意个或两堆拿相同个,若先手必胜,输出先手的取法

思路

威佐夫博弈,完美比例点必败,否则存在两种输出情况,从多的一堆取走一些或者从两堆同时取走一些。

CODE

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;

int main()
{
    ios::sync_with_stdio(0);
    int n,m;
    while (cin>>n>>m)
    {
        if(!n&&!m)break;
        if(n>m)swap(n,m);
        int p=m-n;
        if((int)((sqrt(5.0)+1)/2*p)==n)
        {
            cout<<0<<endl;
        }
        else
        {
            cout<<1<<endl;
            cout<<(int)((sqrt(5.0)+1)/2*p)<<' '<<(int)((sqrt(5.0)+1)/2*p)+p<<endl;
            for(int i=0;i<n;i++)
            {
                p=i*(1+sqrt(5.0))/2;
                if(n-i==p)
                {
                    printf("%d %d\n",n-i,n);
                    break;
                }
            }
        } 
    }
    return 0;
}

取石子游戏——HDU-2516

链接:https://acm.hdu.edu.cn/showproblem.php?pid=2516

大意

一堆n个石子,先取者第一次可以取除全部取完任意多个石子,以后每次取不超过上次取子数的2倍,先取完者胜利。

思路

斐波拉契博弈,值存在于斐波拉契数列中的为必败态。

CODE

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;

int main()
{
    ios::sync_with_stdio(0);
    int n;
    while (cin>>n,n)
    {
        ll p=0,x=1,y=1;
        while(p<n)
        {
            p=x+y;
            swap(x,y);
            y=p;
        }
        if(p==n)
        {
            cout<<"Second win"<<endl;
        }
        else
        {
            cout<<"First win"<<endl;
        }
    }
    return 0;
}

A Multiplication Game——HDU-1517

链接:https://acm.hdu.edu.cn/showproblem.php?pid=1517

大意

两个人操作p=1开始乘,每次乘2~9的数,谁先大于等于n谁胜。

思路

sg函数打表找规律,发现1~9(1 * 9)必胜,10 ~18(1 * 9 * 2)必败,19 ~ 162(1 * 9 * 2 * 9)必胜依次类推

CODE

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;

int main()
{
    ios::sync_with_stdio(0);
    int n;
    while (cin>>n)
    {
        ll ans=1,num=0;
        while(ans<n)
        {
            if(num%2==0)
            {
                ans*=9;
            }
            else
            {
                ans*=2;
            }
            num++;
        }
        if(num&1)
        {
            cout<<"Stan wins."<<endl;
        }
        else
        {
            cout<<"Ollie wins."<<endl;
        }
    }
    return 0;
}

邂逅明下——HDU-2897

链接:https://acm.hdu.edu.cn/showproblem.php?pid=2897

大意

n枚硬币,每次最少取p枚,最多取q枚。取到最后一枚的输!

思路

巴什博弈拓展,只要把板子的(1+q)改成(p+q)即可(顺便加亿点细节)看代码

CODE

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;

int main()
{
    ios::sync_with_stdio(0);
    int n,p,q;
    while (cin>>n>>p>>q)
    {
        if(n%(p+q)>p||n%(p+q)==0)//剩余的大于p,可以先手取到小于p,后面按(p+q)取即可
        {
            cout<<"WIN"<<endl;
        }
        else
        {
            cout<<"LOST"<<endl;
        }
    }
    return 0;
}

A simple stone game——HDU-2486

链接:https://acm.hdu.edu.cn/showproblem.php?pid=2486

大意

一堆n个石头,先取者第一次可以取除全部取完任意多个石子,以后每次取不超过上次取子数的k倍,先取完者胜利。

思路

斐波拉契博弈拓展,k倍动态减法游戏。建一个类似于斐波拉契数列的数列,具体怎么建思路没太懂,先背板子了。数列内存在的数为必败态。

CODE

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;
int a[N],b[N];
int n,k,t,cas=0;
int main()
{
    ios::sync_with_stdio(0);
    cin>>t;
	while(t--)
    {
		cin>>n>>k;
		int i=0,j=0;
		a[0]=b[0]=1;
		while(a[i]<n)
        {
			i++;
			a[i]=b[i-1]+1;
			while(a[j+1]*k<a[i])
				j++;
			if(a[j]*k<a[i])
				b[i]=b[j]+a[i];
			else
				b[i]=a[i];
		}
        cout<<"Case "<<++cas<<": ";
		if(a[i]==n)
			cout<<"lose"<<endl;
		else
        {
			int ans;
			while(n)
            {
				if(n>=a[i])
                {
					n-=a[i];
					ans=a[i];
				}
				i--;
			}
			cout<<ans<<endl;//首发拿的数量
		}
	}
    return 0;
}

Nim or not Nim?——HDU-3032

链接:https://acm.hdu.edu.cn/showproblem.php?pid=3032

大意

n堆石子,每次可以从任意一堆中取任意个石子,也可以将一堆石子分成两小堆,取走最后一个的胜利。

思路

尼姆博弈变形

需要用到一个SJ定理。

对于任意一个Anti-SG游戏,如果定义所有子游戏的SG值为0时游戏结束,先手必胜的条件:
1、游戏的SG值为0且所有子游戏SG值均不超过1
2、游戏的SG值不为0且至少一个子游戏SG值超过1
3、游戏的sg值就是nim的sg值。

每堆石子个数异或表示的什么?其实每堆石子个数就是这堆石子的SG值,所以本质上尼姆博奕是每堆石子的SG值异或运算;所以只需要求出现在的每堆石子SG值再进行异或运算;

SG的运算:SG[x]=mex(SG[y]|y是x的后续(通俗的讲,y是x可以一步到达的状态));mex()函数是求集合中未出现的最小非负整数;如mex(0,2,3)=1;

打表得到规律:

SG[4k+1]=4k+1;

SG[4k+2]=4k+2;

SG[4k+3]=4k+4;

SG[4k+4]=4k+3;

CODE

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;

int main()
{
    ios::sync_with_stdio(0);
	int t;
    cin>>t;
	while(t--)
    {
		int n;
		cin>>n;
		ll ans=0;
		for(int i=1;i<=n;i++)
		{
			int x;
			cin>>x;
			if(x%4==0)
			{
				ans^=x-1;
			}
			else if(x%4==3)
			{
				ans^=x+1;
			}
			else
			{
				ans^=x;
			}
		}
		if(!ans)
		{
			cout<<"Bob"<<endl;
		}
		else
		{
			cout<<"Alice"<<endl;
		}
	}
    return 0;
}

Climbing the Hill——HDU-4315

链接:https://acm.hdu.edu.cn/showproblem.php?pid=4315

大意

在山上有n个人,每个人编号是1~n,这些位置只能同时被一个人占据,但是山顶可以同时被多个人占据,距离山顶第k近的是King,现在Alice和Bob开始向上送人,条件是不能跨越前面最近的人,问在Alice先手,双方最优的条件下谁能把King送到山顶。

思路

阶梯尼姆,头疼。。。之前写了个蓝桥杯一道阶梯尼姆题《高僧斗法》的博客,遇到这道题还是没做出来。

阶梯尼姆的主要思想为两两配对后求异或,这题为送国王到山顶的获胜。

所以我们考虑:

1、国王在第一位时,先手必胜。

2、国王在第二位时且n为奇数时,从后往前两两配对,那么第一个人最多只能停在a[1]上 , 因为如果第一个人爬到山顶,那么king就可以直接上山顶了,所以这题可以转化为 , 谁必须移动第一个人谁必败。所以需要减1。

3、其他情况,两两配对正常异或即可。

CODE

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+7;
int a[N];
int main()
{
    ios::sync_with_stdio(0);
	int n,m;
	while(cin>>n>>m)
	{
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];
		}
		if(m==1)
		{
			cout<<"Alice"<<endl;
			continue;
		}
		if(n&1)
		{
			int p=a[1];
			if(m==2)	p--;
			for(int i=2;i+1<=n;i+=2)
			{
				p^=(a[i+1]-a[i]-1);
			}
			if(p)
			{
				cout<<"Alice"<<endl;
			}
			else
			{
				cout<<"Bob"<<endl;
			}
		}
		else
		{
			int p=a[2]-a[1]-1;
			for(int i=3;i+1<=n;i+=2)
			{
				p^=(a[i+1]-a[i]-1);
			}
			if(p)
			{
				cout<<"Alice"<<endl;
			}
			else
			{
				cout<<"Bob"<<endl;
			}
		}
	}
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

第十页

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值