题目大意
两人轮流取一堆
n
n
n个石子,先手不能全部取完,第一次之后每人取的个数不能超过
另一个人上轮取的数
×
k
\text{另一个人上轮取的数} \times k
另一个人上轮取的数×k,问限售是否有必胜策略。若有,输出输出第一次最小取的石子数。
对于
10
%
10\%
10%的数据
k
=
1
k=1
k=1;
对于
30
%
30\%
30%的数据
1
≤
k
≤
2
1 \leq k \leq 2
1≤k≤2;
对于
100
%
100\%
100%的数据
2
≤
n
≤
1
0
8
,
1
≤
k
≤
1
0
5
2 \leq n \leq 10^8,1 \leq k \leq 10^5
2≤n≤108,1≤k≤105。
分析
这是一道博弈论题。
因为此题有部分分,所以我们先从部分分入手。
10分
对于 10 % 10\% 10%的数据有 k = 1 k=1 k=1,所以对方每次拿的数量都不能超过你上次拿的数量。于是我们可以先打一个表找 k = 1 k=1 k=1的规律:
#include<cstdio>
int dfs(int n,int mn)//搜索,返回值:-1表示必输,其他表示必胜策略第一次最小取的石子数
{
if(n<=0)//结束判断
{
return -1;
}
for(int i=1;i<=mn;i++)//枚举可行方案
{
if(dfs(n-i,i)==-1)//判断是否必胜
{
return i;//必胜
}
}
return -1;//无法胜利,即必败
}
int main()
{
for(int i=1;i<=20;i++)
{
int t=dfs(i,i-1);
if(t!=-1)
{
printf("%d\n",t);
}
else
{
printf("lose\n");
}
}
return 0;
}
运行代码得:
n n n | 结果 | n n n | 结果 |
---|---|---|---|
1 1 1 | l o s e lose lose | 11 11 11 | 1 1 1 |
2 2 2 | l o s e lose lose | 12 12 12 | 4 4 4 |
3 3 3 | 1 1 1 | 13 13 13 | 1 1 1 |
4 4 4 | l o s e lose lose | 14 14 14 | 2 2 2 |
5 5 5 | 1 1 1 | 15 15 15 | 1 1 1 |
6 6 6 | 2 2 2 | 16 16 16 | l o s e lose lose |
7 7 7 | 1 1 1 | 17 17 17 | 1 1 1 |
8 8 8 | l o s e lose lose | 18 18 18 | 2 2 2 |
9 9 9 | 1 1 1 | 19 19 19 | 1 1 1 |
10 10 10 | 2 2 2 | 20 20 20 | 4 4 4 |
观察结果,我们可以发现:必输的局面的石子数都可表示为
2
i
2^i
2i(
i
i
i为非负整数),即必输的局面为
n
=
1
,
2
,
4
,
8
,
16
⋯
n=1,2,4,8,16 \cdots
n=1,2,4,8,16⋯时。同时,必胜策略第一次最小取的石子数就是
n
n
n二进制下从右往左数第一个“
1
1
1”所代表的十进制数字,如:
3
(
10
)
=
1
1
(
2
)
⇒
n
=
3
时的答案为
1
(
2
)
=
1
(
10
)
3_{(10)}=11_{(2)} \Rightarrow n=3 \text{时的答案为} 1_{(2)}=1_{(10)}
3(10)=11(2)⇒n=3时的答案为1(2)=1(10),
6
(
10
)
=
11
0
(
2
)
⇒
n
=
6
时的答案为
1
0
(
2
)
=
2
(
10
)
6_{(10)}=110_{(2)} \Rightarrow n=6 \text{时的答案为} 10_{(2)}=2_{(10)}
6(10)=110(2)⇒n=6时的答案为10(2)=2(10),
1
0
(
10
)
=
101
0
(
2
)
⇒
n
=
10
时的答案为
1
0
(
2
)
=
2
(
10
)
10_{(10)}=1010_{(2)} \Rightarrow n=10 \text{时的答案为} 10_{(2)}=2_{(10)}
10(10)=1010(2)⇒n=10时的答案为10(2)=2(10)……
其实这可以被证明:
把 n n n转成二进制,例如 n = 2 5 ( 10 ) = 1100 0 ( 2 ) n=25_{(10)}=11000_{(2)} n=25(10)=11000(2)。
那么我们每次拿掉最末尾的一个 1 1 1,例子拿完后 n = 2 4 ( 10 ) = 1100 0 ( 2 ) n=24_{(10)}=11000_{(2)} n=24(10)=11000(2)。
因为对方每次拿的数量都不能超过你上次拿的数量,所以对方不能拿更高层的 1 1 1。这样我们每一次都拿走最末尾的 1 1 1,对方就只能拿 0 0 0。例子中对方拿完后 n = 2 3 ( 10 ) = 1011 1 ( 2 ) n=23_{(10)}=10111_{(2)} n=23(10)=10111(2)。
而对方每次拿完 0 0 0后又会产生新的 1 1 1,一直到最后一个 1 1 1被我们拿走。
必败情况就是 2 i 2^i 2i( i i i为非负整数),例如: n = 3 2 ( 10 ) = 10000 0 ( 2 ) n=32_{(10)}=100000_{(2)} n=32(10)=100000(2)。
因为不能一次性拿完,那么我们只能拿走二进制中的 0 0 0,对方就可以每次拿走二进制末尾的 1 1 1。
——改编自 C S D N CSDN CSDN博客
由我们发现的规律可得 10 10 10分的代码:
#include<cstdio>
int lowbit(int x)//求x二进制下从右往左数第一个“1”所代表的十进制数字
{
return x&-x;
}
int main()
{
int T;
scanf("%d",&T);
for(int Case=1;Case<=T;Case++)//注意多组数据
{
printf("Case %d: ",Case);
int n,k;
scanf("%d%d",&n,&k);//读入n,k
if(k==1)//当k=1时
{
if(lowbit(n)==n)//当n二进制下从右往左数第一个“1”所代表的十进制数字为n,即n可表示为2^i(i为非负整数)时
{
printf("lose\n");//必输
}
else
{
printf("%d\n",lowbit(n));//有必胜策略
}
}
}
return 0;
}
30分
对于
30
%
30\%
30%的数据有
1
≤
k
≤
2
1 \leq k \leq 2
1≤k≤2。其中
k
=
1
k=1
k=1的情况我们已经在
10
%
10\%
10%的数据中讨论了,所以我们考虑
k
=
2
k=2
k=2的情况。
同理,我们也可以打一个表找
k
=
2
k=2
k=2的规律:
#include<cstdio>
int dfs(int n,int mn)//搜索,返回值:-1表示必输,其他表示必胜策略第一次最小取的石子数
{
if(n<=0)//结束判断
{
return -1;
}
for(int i=1;i<=mn;i++)//枚举可行方案
{
if(dfs(n-i,i*2)==-1)//判断是否必胜
{
return i;//必胜
}
}
return -1;//无法胜利,即必败
}
int main()
{
for(int i=1;i<=20;i++)
{
int t=dfs(i,i-1);
if(t!=-1)
{
printf("%d\n",t);
}
else
{
printf("lose\n");
}
}
return 0;
}
运行代码得:
n n n | 结果 | n n n | 结果 |
---|---|---|---|
1 1 1 | l o s e lose lose | 11 11 11 | 3 3 3 |
2 2 2 | l o s e lose lose | 12 12 12 | 1 1 1 |
3 3 3 | l o s e lose lose | 13 13 13 | l o s e lose lose |
4 4 4 | 1 1 1 | 14 14 14 | 1 1 1 |
5 5 5 | l o s e lose lose | 15 15 15 | 2 2 2 |
6 6 6 | 1 1 1 | 16 16 16 | 3 3 3 |
7 7 7 | 2 2 2 | 17 17 17 | 1 1 1 |
8 8 8 | l o s e lose lose | 18 18 18 | 5 5 5 |
9 9 9 | 1 1 1 | 19 19 19 | 1 1 1 |
10 10 10 | 2 2 2 | 20 20 20 | 2 2 2 |
观察结果,我们可以发现:必输的局面的石子数都在斐波那契数列(
F
i
b
o
n
a
c
c
i
s
e
q
u
e
n
c
e
Fibonacci \space sequence
Fibonacci sequence)上,即必输的局面为
n
=
1
,
2
,
3
,
5
,
18
,
13
⋯
n=1,2,3,5,18,13 \cdots
n=1,2,3,5,18,13⋯时。同时,必胜策略第一次最小取的石子数就是
n
n
n表示成斐波那契数列中若干个互不相邻的数的和后,选出的数中的最小数,如:
6
=
1
+
5
⇒
n
=
6
时的答案为
1
6=1+5 \Rightarrow n=6 \text{时的答案为} 1
6=1+5⇒n=6时的答案为1,
16
=
3
+
13
⇒
n
=
16
时的答案为
3
16=3+13 \Rightarrow n=16 \text{时的答案为} 3
16=3+13⇒n=16时的答案为3,
20
=
2
+
5
+
13
⇒
n
=
20
时的答案为
2
20=2+5+13 \Rightarrow n=20 \text{时的答案为} 2
20=2+5+13⇒n=20时的答案为2……
这个的证明如下:
因为斐波那契数列有一个性质:把任意一个数 n n n解成数列中互不相邻的数相加,分解出的数两两都相差 2 2 2倍以上(不包括 2 2 2倍),
所以我们把 n n n分解成斐波那契数列后,先手取出最小数个石子后,对方就无法取出次小数个石子,而先手可以取到第 最小数 + 次小数 \text{最小数} + \text{次小数} 最小数+次小数个石子;
如此循环,则先手可以取完这 n n n个石子。
——改编自 C S D N CSDN CSDN博客
由我们发现的规律可得 30 30 30分的代码:
#include<cstdio>
int lowbit(int x)//求x二进制下从右往左数第一个“1”所代表的十进制数字(k=1时用)
{
return x&-x;
}
int fib[10000001];//斐波那契数列(k=2时用)
int main()
{
int T;
scanf("%d",&T);
for(int Case=1;Case<=T;Case++)//注意多组数据
{
printf("Case %d: ",Case);
int n,k;
scanf("%d%d",&n,&k);//读入n,k
if(k==1)//当k=1时
{
if(lowbit(n)==n)//当n二进制下从右往左数第一个“1”所代表的十进制数字为n,即n可表示为2^i(i为非负整数)时
{
printf("lose");//必输
}
else
{
printf("%d",lowbit(n));//有必胜策略
}
}
else
{
if(k==2)//当k=2时
{
fib[0]=1;//递推求斐波那契数列
fib[1]=1;
int len=1;
while(fib[len]<n)
{
fib[len+1]=fib[len]+fib[len-1];
len++;
}
if(fib[len]==n)//当n在斐波那契序列中时
{
printf("lose");//必输
}
else
{
for(int i=len-1;;i--)//分解n
{
if(n>=fib[i])
{
n=n-fib[i];
if(n==0)//当找到最小数时
{
printf("%d",fib[i]);//输出
break;
}
}
}
}
}
}
printf("\n");
}
return 0;
}
100分
既然
30
%
30\%
30%的数据都与数列有关,那么
100
%
100\%
100%的数据是不是也与数列有关呢?我们尝试寻找,却没有发现。于是我们要构造一个。
我们设数列
a
a
a为我们构造的数列,他满足可以选这个序列中的一些数相加来表示任意正整数,并且选出的每两个数都满足其中一个是另一个的
k
k
k倍以上(不包括
k
k
k倍)。同时,我们设数列
b
b
b,其中
b
i
b_i
bi为用
a
1
,
a
2
,
⋯
,
a
i
a_1,a_2, \cdots , a_i
a1,a2,⋯,ai中的一些数相加能表示的最大正整数,其中选出的每两个数都满足其中一个是另一个的
k
k
k倍以上(不包括
k
k
k倍)。初始时,
a
1
=
b
1
=
1
a_1=b_1=1
a1=b1=1。
然后我们来推
a
i
,
b
i
a_i,b_i
ai,bi(
i
≥
2
i \geq 2
i≥2)的表达式。由序列
b
b
b的定义可知,用
a
1
,
a
2
,
⋯
,
a
x
a_1,a_2, \cdots , a_x
a1,a2,⋯,ax中的一些数相加能表示的最大正整数为
b
x
b_x
bx,其中选出的每两个数都满足其中一个是另一个的
k
k
k倍以上(不包括
k
k
k倍),则
b
x
+
1
b_x+1
bx+1就无法用
a
1
,
a
2
,
⋯
,
a
x
a_1,a_2, \cdots , a_x
a1,a2,⋯,ax按上述规则表示。那么我们要将
b
x
+
1
b_x+1
bx+1加入序列
a
a
a中,由此可得
a
i
a_i
ai的表达式:
a
i
=
b
i
−
1
+
1
a_i=b_{i-1}+1
ai=bi−1+1。又因为
b
i
b_i
bi要满足最大,所以我们要找一个最大的
t
t
t,使得
a
t
×
k
<
a
i
a_t \times k < a_i
at×k<ai,则
b
i
=
b
t
+
a
i
b_i=b_t+a_i
bi=bt+ai。但是有时我们找不到一个满足要求的
t
t
t,这时的
b
i
b_i
bi就为
a
i
a_i
ai。
举个例子:
当 k = 3 k=3 k=3时,有:
i i i a i a_i ai b i b_i bi i i i a i a_i ai b i b_i bi 1 1 1 1 1 1 1 1 1 6 6 6 8 8 8 10 10 10 2 2 2 2 2 2 2 2 2 7 7 7 11 11 11 14 14 14 3 3 3 3 3 3 3 3 3 8 8 8 15 15 15 20 20 20 4 4 4 4 4 4 5 5 5 9 9 9 21 21 21 28 28 28 5 5 5 6 6 6 7 7 7 10 10 10 29 29 29 39 39 39
这样一来,我们就可以轻松求解了:与
30
%
30\%
30%的数据同理,若
n
n
n在序列
a
a
a中,则先手必输;否则用
a
a
a中的数分解
n
n
n,且分解出的每两个数都满足其中一个是另一个的
k
k
k倍以上(不包括
k
k
k倍),必胜策略的第一次最小取的石子数就是分解出的最小数。
代码如下:
#include<cstdio>
int a[10000001],b[10000001];//序列a,b
int main()
{
int T;
scanf("%d",&T);
for(int Case=1;Case<=T;Case++)//注意多组数据
{
printf("Case %d: ",Case);
int n,k;
scanf("%d%d",&n,&k);//读入n,k
a[1]=b[1]=1;//初始化序列a,b
int len;
for(len=1;a[len]<n;len++)//构造序列a,b
{
a[len+1]=b[len]+1;//构造序列a
if(a[1]*k>=a[len+1])//当选序列a中的最小数(a[1])后仍无法选最后一个数时
{
b[len+1]=a[len+1];//b[i]=a[i]
}
else
{
int l=1,r=len;//二分查找t,因为a具有单调递增性
while(l<r)
{
int mid=(l+r+1)/2;
if(a[mid]<double(a[len+1])/k)//原来是a[mid]*k<a[len+1](判断选了a[mid]后能否选a的最后一个),注意防止int溢出
{
l=mid;//能选,再往后找
}
else
{
r=mid-1;//能选,往前找
}
}
b[len+1]=b[l]+a[len+1];//b[i]=b[t]+a[i]
}
}
if(n==a[len])//当n在序列a中时
{
printf("lose");//必输
}
else
{
for(int i=len-1;i>=1;i--)//分解n
{
if(n>=a[i])
{
n-=a[i];
if(n==0)
{
printf("%d",a[i]);//输出最小数
break;
}
}
}
}
printf("\n");
}
return 0;
}
总结
做博弈论的题时,要先从小范围的数据中找规律,然后再推广到大范围的数据得出结论。