对博弈论还不是那么明白的小伙伴请先阅读 这篇文章
题目
Luogu: P4101 [HEOI2014]人人尽说江南好
游戏的规则是这样的,给定 N 堆石子,每堆石子一开始只有 1 个。小 Z 和他的小伙伴轮流操作, 小 Z 先行操作。操作可以将任意两堆石子合并成为一堆,当谁不再能操作的时候,谁就输掉了。
不过,当一堆石子堆的太高时可能发生危险,因此小 Z 和他的小伙伴规定,任何时刻任意一 堆石子的数量不能超过 m。即假如现在有两堆石子分别有 a 个和 b 个,而且 a+b>m,那么这 两堆石子就不能合成一堆。
小 Z 和他的小伙伴都是很聪明的,所以他们总是会选择对自己最有利的策略。现在小 Z 想要知道,在这种情况下,对于一个给定的 n 和 m,到底是谁能够获得胜利呢?
算法分析
先手和后手要依次合并石子,所以每次堆数都会减少1。
当
n
<
=
m
n<=m
n<=m 时,显然,
n
n
n个石子是可以被放在一堆里面的,所以合并次数
a
n
s
=
n
−
1
;
ans=n-1;
ans=n−1;
当
n
>
m
n>m
n>m 时,将
n
n
n个石子合并后的结果会是这个样子
{
m
,
m
,
m
,
m
,
.
.
.
n
%
m
}
\{ m\ ,\ m\ ,\ m\ ,\ m\ ,...\ n\ \%\ m\ \}\
{m , m , m , m ,... n % m }
所以合并次数
a
n
s
=
(
n
/
m
)
∗
(
m
−
1
)
+
(
(
n
%
m
)
?
(
n
%
m
−
1
)
:
0
)
ans=(n/m)*(m-1)+(\ (n\%m)\ ?\ (\ n\%m-1):0\ )
ans=(n/m)∗(m−1)+( (n%m) ? ( n%m−1):0 )
说完合并次数的问题,我们来聊一聊最优策略的问题。
本题中当 a n s ans ans为奇数时,先手获胜,反之,后手获胜。
当n ≤ m时,这种情况最后肯定能合成一堆,我们称这个较大的堆为大堆。假如现在轮到先手操作,先手还没动,这个时候最长合并次数为偶数次,那么先手有两种可能性,把后面一个堆丢进大堆里面,这样后手再丢一个小堆进去,或者把后面两个堆合成一个,那么后手就可以把这个合成的直接丢进去。无论怎么做,后手都能保证每轮完了之后,大堆的石子会增加两个,那么合并次数也会-2,一直保持为偶数。直到最后先手合无可合。
具体代码如下:(非常的简洁)
Code
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rg register
typedef long long ll;
inline int sread()
{
ll x=0,f=1;char c=getchar();
while(c>'9'||c<'0') {if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9') {x=x*10+c-'0';c=getchar();}
return f*x;
}
ll n,m,T,ans;
int main()
{
T=sread();
for(rg int e=1;e<=T;++e)
{
n=sread();m=sread();
if(n<=m) ans=n-1;
else {
ans=(n/m)*(m-1)+((n%m)?(n%m-1):0);
}
if(ans%2) printf("0\n");
else printf("1\n");
}
return 0;
}
反思与总结
要寻找到最优的策略,是这类题的突破口。本题中合并次数就是最优策略的发现点。要善于发现题目中的博弈方式和最优策略。