【博弈找规律问题汇总】

HDU1847 —— Good Luck in CET-4 Everybody!

题意:n张牌,轮流抓牌,每次抓的牌数是2的幂次,最后抓完牌的胜。 ( 1 n 1000 ) (1\leq n\leq 1000)
思路:首先写在这个汇总题集的最前面,博弈问题,大致分为三类,① 经典模型 ② SG函数 ③ 找规律,而此题集主要针对的也是此类找规律问题。
对于此题,的确可以用 S G SG 函数解决,但是我们可以先找一下规律,不要着急下手。(实测,SG函数会T…)
此处用W表示Win,F表示Fail。1 - W 、2 - W、3 - F、4 - W、5 - W、6 - F…列的再多一些,就可以明显地发现当n为3倍数时,Fail,否则Win。这也是找规律的常见手段,枚举状态找规律。
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const int N = 1000+100;
const int M = 1e5+100;
const db EPS = 1e-9;
using namespace std;

int n;

int main()
{
	while(~scanf("%d",&n))
	{
		if(n%3 == 0) printf("Cici\n");
		else printf("Kiki\n");
	}
	return 0;
}

/*
n张牌,轮流抓牌,每次抓的牌数是2的幂次,最后抓完牌的胜
*/

HDU2147 —— kiki’s game

题意:推方格,从(n,m)开始推,每次只能往左一格,往下一格,往左下一格,不能推了则为输。 ( 0 &lt; n , m 2000 ) (0&lt;n,m\leq 2000)
思路:博弈问题本质是个游戏,拿到问题的第一步应该是对于小状态进行模拟,很多简单找规律的问题都会迎刃而解。

在这里插入图片描述

1 1 表示必胜态, 0 0 表示必败态,可以发现 0 0 出现的位置是固定的,即行数列数均为奇数的地方,由此此题解决。
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const int N = 2000+1;
const int M = 1e5+100;
const db EPS = 1e-9;
using namespace std;

int n,m;

int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		if(n == 0 || m == 0) break;
		if(n%2 && m%2) printf("What a pity!\n");
		else printf("Wonderful!\n");
	}
	return 0;
}

/*
推方格,从(n,m)开始推,每次只能往左一格,往下一格,往左下一格,不能推了则为输。
*/

POJ1740 —— A New Stone Game

题意:对于n堆石子,每堆若干个,两人轮流操作,每次操作分两步。① 从某堆中去掉至少一个 ② (可省略) 把该堆剩余石子的一部分分给其它的某些堆。最后谁无子可取即输。
思路:此时可以先考虑只有两堆石子的时候,应该如何判断。不难发现,假如两堆石头一样多,则后者可以完全模仿前者的操作 (“模仿”与“两两分组”是最常见的博弈手段之一),因此我们可以考虑将相同堆数的石头进行两两分组。
不难发现,如果石头堆数为偶数个,且恰好可以两两分组,并且每组中石头个数一致,则一定是必败态。即下图这种情况。

在这里插入图片描述

然后我们来证明其他所有情况均可转化为必胜态。假如 n n 为奇数,则一定可以利用最多的那堆石子,将其他 n 1 n-1 堆石子进行两两分组。如下图所示,利用A部分使得第一堆与第二堆相同,利用B部分使第三堆和第四堆相同。

在这里插入图片描述

如果石子个数为偶数,但是并不能恰好两两分组,则将最大的一堆石子与最小的那堆石子进行匹配,然后利用最大的那堆石子填充其余每组石子之间的差距,构造出一个必败态。
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const int N = 1e5+100;
const int M = 1e5+100;
const db EPS = 1e-9;
using namespace std;

int n,vis[N];

int main()
{
	while(~scanf("%d",&n)){
		if(!n) break;
		rep(i,0,100) vis[i] = 0;
		rep(i,1,n){
			int xx; scanf("%d",&xx);
			vis[xx]++;
		}
		if(n%2 == 1){
			printf("1\n");
			continue;
		} 
		int jud = 0;
		rep(i,0,100){
			if(vis[i]%2 == 1) jud = 1;
		}
		if(jud) printf("1\n");
		else printf("0\n");
	}
	return 0;
}

/*
对于n堆石子,每堆若干个,两人轮流操作,每次操作分两步.
第一步从某堆中去掉至少一个.
第二步(可省略)把该堆剩余石子的一部分分给其它的某些堆。最后谁无子可取即输。
*/

HDU4388 Stone Game II

题意 n n 堆石子,每堆石子有一定的数目,每次操作任选一堆石子,该堆石子原数目为 x x ,取石子至仅剩 k k 个,要求k ^ x < x。取完石子后再增加一堆石子,石子个数为k ^ x。每个选手在每轮游戏中仅有一次机会将增加的那一堆石子个数改为2*k^x。问谁能获胜。
思路:这个题的主要难点在于如何处理k ^ x < x的问题,这个限制有什么性质。因此我们需要打表,查看k与k ^ x与x的关系,打表之后可以发现无论如何取,x与k ^ x二进制形式中 1 1 的个数与 x x 1 1 的个数奇偶性相同。
假设一堆个数为 x x 的石子,二进制中有 k k 1 1 ,最后分为了 m m 堆,由于最后每一堆石子个数二进制形式中都只有 1 1 1 1 ,因此 m m k k 奇偶性相同。而 m m 堆即分了 m 1 m-1 次,所以通过 m m 的奇偶性即可判定先手胜负。
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const int N = 1e5+100;
const int M = 1e5+100;
const db EPS = 1e-9;
using namespace std;

int countt(int x){
  int num = 0;
  while(x){
    if(x&1) num++; 
    x >>= 1;
  }
  return num;
}

int main()
{
  int _,n,tp; scanf("%d",&_);
  rep(kk,1,_){
    scanf("%d",&n); tp = 0;
    rep(i,1,n){
      int xx; scanf("%d",&xx);
      tp += countt(xx)-1;
    }
    printf("Case %d: ",kk);
    if(tp%2) printf("Yes\n");
    else printf("No\n");
  }
}

/* 打表程序
int main()
{
  int n = 100;
  rep(i,1,n){
    printf("************\n");
    LOG2("i",i,"num",countt(i));
    rep(j,1,i-1){
      if((j^i) < i){
        LOG3("j",j,"j^i",(j^i),"num",(countt(j)+countt(j^i)));
      }
    }
  }
  return 0;
}*/

总结:

找规律问题经常在博弈问题中出现,不属于特定的模型,但是需要找到规律。而找规律通常的方法有 ① 小状态枚举 ② 打表找规律 ③ 奇偶讨论、两两分组、模仿先手行为等策略。

猜你喜欢

转载自blog.csdn.net/qq_41552508/article/details/89096602