初始了解了一些博弈,根据特定的规则,学习了一些只会和规律
首先是 巴什博弈:
HDU1846,最基本的先手后手赢问题,对于一堆石子我要采取什么拿的方法才能让对方最后无子可那呢,那就是在我拿完之后尽可能的让n是m+1的倍数(n是总数,m 是我能拿的上限)
int flag = n % (m + 1);
if(flag >= 1)cout<<"first"<<endl;
else cout<<"second"<<endl;
然后HDU2149,换了一种问法,多了一种特殊的判断可拿的大于已有的
int flag = m % (n + 1);
if(flag == 0)cout<<"none"<<endl;
else
{
printf("%d",flag);
if(flag == m)
{
for(int i = flag + 1;i <= n;i++)
{
printf(" %d",i);
}
}
printf("\n");
}
接下来又去看了HDU2147,换成了一种棋盘的问题,这里又学习了必败点P和比胜点N的关系,
定理:
一、所有终结点都是必败点P
二、所有一步能走到必败点P的就是N点;
三、通过一步操作只能到N点的就是P点;
那就想一想棋盘上的必败点和必胜点的分布
NNNNNNNNN
PNPNPNPNP
NNNNNNNNN
PNPNPNPNP
NNNNNNNNN
PNPNPNPNP
NNNNNNNNN
PNPNPNPNP
总结处比较简单的规律了
cin>>n>>m;
int flag = n % (m + 1);
if(!flag)cout<<"Rabbit"<<endl;
else cout<<"Grass"<<endl;
如果行数和列数都为奇数的时候,那么必败
然后是斐波那契博弈
1堆石子有n个,两人轮流取.先取者第1次可以取任意多个,但不能全部取完.以后每次取的石子数不能超过上次取子数的2倍。
斐波那契取石子问题,有一个定理齐肯多夫定理):任何正整数可以表示为若干个不连续的Fibonacci数之和。
分析后,会得到如下规律,一开始如果是斐波那契数的话,那么后手胜利,不是的话,先手胜利,查找斐波那契数可以用二分查找
接下来威佐夫博弈
这是两堆物品,出现了奇异局势(必败局势)—— (a,b)
对于a,b两堆判断位必败局势的方法是
if(a > b)swap(a,b);
c = int((b - a)*((sqrt(5.0) + 1) / 2));
if(c == a)puts("0");
else puts("1");
如何得出的我也还没消化,暂且记录以下结论
接下来是Nim博弈
取N堆石子的问题
我们可以将每一个数字写成二进制形式,如果每一个二进制位上1的个数都相等(称为平衡态),无论采取怎样的拿法,只会把其变为
一个存在某些二进制位上1的个数不相等的状态(称为非平衡态)
用异或去做,n堆石子的异或和如果位0那么先手必败,否则先手胜利,这是HDU1850
for(int i = 1;i <= s;i++)
{
cin>>a[i];
ans ^= a[i];
}
if(ans == 0)
{
puts("0");
continue;
}
知道ans != 0后这个题还没完,还问我第一步有几种方案
for(int i = 1;i <= s;i++)
{
int k = ans ^ a[i];
if(k < a[i])cnt++;
}
也就是遍历每一堆石子,异或后的值如果小,那么就能得到,这一堆的石子够我那了
然后我又补了一道NIM博弈的题目ZOJ3964,也是NIM问题,但是有了限制,对于限制的分析成立我们的解题点
对于限制,如果我们不去消除,那么最后只会让对方对我们进行控制,我们就必败
所以借着先手的优势,能一步解决我们的限制,才有赢得希望,所以我们得知道:
如果限制多余1个那么不用考虑,必败
那就一个限制呢,就得看看能不能一次性解决问题,对于限制2,我只能拿偶数,恰好那一堆是奇数,完了,限制解除不了,必败,恰巧位偶数呢,就可以按照正常的Nim了,只不过先后手要注意一下
对于限制1,就没那么多事了,一次拿光也好,剩下一个也好,我和对手都是平等的了,不存在劣势了,可以一搏
没有限制,正常的NIM操作
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
const int N = 1e5 + 10;
int a[N],b[N],n;
bool judge(int cnt1,int cnt2)
{
int tot = 0;
if(cnt1 == 1)
{
for(int i = 1;i <= n;i++)
{
if(b[i] == 1 && a[i] > 1)//相当于bob先手得nim
{
if(a[i] & 1)
{
tot ^= 0;
}
else
{
tot ^= 1;
}
}
else
{
tot ^= a[i];
}
}
if(tot == 0)return false;
else return true;
}
else if(cnt2 == 1)
{
for(int i = 1;i <= n;i++)
{
if(b[i] != 2)//前面已经判断力如果a[i]为奇数得话,必输
{
tot ^= a[i];
}
}
if(tot == 0)return false;
else return true;
}
else
{
for(int i = 1;i <= n;i++)
{
tot ^= a[i];
}
if(tot == 0)return true;
else return false;
}
}
int main()
{
int T;
while(~scanf("%d",&T))
{
while(T--)
{
scanf("%d",&n);
int cnt2 = 0,cnt1 = 0,cnt0 = 0;
for(int i = 1;i <= n;i++)
{
scanf("%d",&a[i]);
}
for(int i = 1;i <= n;i++)
{
scanf("%d",&b[i]);
if(b[i] == 2)
{
cnt2++;
cnt0++;
if(a[i] & 1)cnt0 = 99;
}
if(b[i] == 1 && a[i] > 1)//a[i] = 1时相当于没有限制
{
cnt1++;
cnt0++;
}
}
if(cnt0 > 1 || judge(cnt1,cnt2))
{
printf("Bob\n");
}
else
{
printf("Alice\n");
}
}
}
return 0;
}