取火柴问题变种
目录
- 起因
- 问题构建 & 前置知识
- 手推过程
- 过程
- 验证结论
- 补充
起因
高中毕业,整理材料的时候,发现自己之前在草稿纸上探究到一半的变种取火柴问题。于是就展开来探究了一下。
问题构建 & 前置知识
正常的取火柴问题是:一堆火柴一共有 根,有两个足够聪明的人轮流取火柴,每次可以取 根,取到最后一根火柴的人胜利,问先手必胜还是后手必胜。
例如,每次可以取 根。很容易知道,当 时,后手必胜,否则先手必胜。也就是说,对于取 的情况,当且仅当 是 的倍数时,后手必胜。这里不展开赘述。
现在我们要讨论的是,如果在 中跳过一个数不能取,周期会发生什么变化?如果跳过不止一个数,周期又会发生什么变化?
在后续的问题探讨中,可以采用线性递推的方式,例如,你已经知道总火柴数为 时分别的必胜情况,那么对于总数 的情况,只要能够一次取若干根跳到一个后手必胜的局面,那么该情况一定是先手必胜;若找不到,则后手必胜。
手推过程
跳过一个数
问题变为:每次可以取 或
这个问题依然比较经典,经过几个例子的尝试后可以发现,后手必胜情况的周期为断点位置,即后手必胜当且仅当 为正整数。
后面不止一个数
问题变为:每次可以取 或
我们尝试举例。
尝试 :后手必胜周期
尝试 :后手必胜周期 交替,大周期
注意到,我们似乎需要比较前后两段数字的长短,也就是 和 的大小关系。
似乎,前半段较短时,会出现交替后手必胜周期;后半段较短时则不会?
尝试 :后手必胜周期
尝试 :后手必胜周期 交替,大周期
推论:
- 当 :后手必胜周期
- 当 :后手必胜周期 交替,大周期
经过多轮验证,应该成立。
开头一般化
问题变为:每次可以取 或
这种情况不符合题目情况,总数为特定数时会出现最后几根怎么也取不走的情况,游戏无法进行。
故我们不讨论这种情况。
跳过若干数
问题变为:每次可以取 或
我们毫无头绪,于是举例:
尝试 :后手必胜周期
尝试 :后手必胜周期 交替,大周期
尝试 :后手必胜周期
尝试 :后手必胜周期
尝试 :后手必胜周期 交替,大周期
尝试 :后手必胜周期
尝试 :后手必胜周期
尝试 :后手必胜周期 交替,大周期
尝试 :后手必胜周期 交替,大周期
尝试 :后手必胜周期 交替,大周期
尝试 :后手必胜周期 交替,大周期
尝试 :后手必胜周期 交替,大周期
尝试 :后手必胜周期 交替,大周期
尝试 :后手必胜周期 交替,大周期
尝试 :后手必胜周期 ,大周期
尝试 :后手必胜周期 ,大周期
尝试 :后手必胜周期
尝试 :后手必胜周期 ,大周期
感觉似乎是有规律的,但是我搞不明白,于是,我转向了 。
过程
经过 ,我们发现,当取 的时候:
如果不考虑 的部分,大于 的部分如果不是 的倍数,一定先手胜,所有 的倍数都是后手胜。
加入考虑, 中可能影响后手胜位置的,便是其中是 倍数的数。例如含有 ,那么此时比 略大的数,跳到的 就会变成先手胜。
这个时候我们需要讨论,通过 和 两个例子,我们发现前者是 , 接在后边构成 大周期,后者是 把 包在里面形成 大周期。
于是我又 了一下,对于 ,记 ,他给出的结论是:
if 后半段连续区间包含m*L(m为能取所有值的最小值): if m>=2 and 后半段区间长>=L: T=(m-1)L+(b+1) 接尾型 else: T=b+1 包裹型else: T=a+1 断点型对于分支一,通俗的解释是: 覆盖所有可到达的非倍数余数,如果无 的倍数,那么所有后手胜的位置都不会受到影响,等于是先手胜的位置自己之间加了一些连接。
对于分支二,通俗的解释是: 是能减去的最大数, 之前的数都按照 的倍数规则,其中 全部标为先手胜,那么如果 跳不到任何后手胜点,他自己就是后手胜,否则会被推迟到 。
验证结论
验证方案
验证规则成立:对拍
对拍,具体来说就是,对比“暴力递推”和“规律求解”两种方法得出的周期是不是都相同。
输入
输入所有可以取的数,然后输出 为 分别是谁必胜,记先手必胜为 ,后手必胜为 。
规律求解法
于是我先写了一个按照规则输出周期的代码 answer.cpp 。
暴力递推法
在写这个代码的时候,我发现,我需要根据获得的 数组来求它的最小周期,这个时候我们可以使用 匹配算法,此处不展开讲述,代码写入 duipai.cpp 。
随机数据生成
这里我们使用 C++ 中的均匀整数生成工具,代码如 random.cpp 。
比对数据
我写了一个程序,让他们自动生成 次数据,比对 次,看结果是否有差异。代码如 check.cpp 。
参考代码
所有代码如下:
#include<bits/stdc++.h>using namespace std;int n,A[105],m,L,a,c,b,T;bool flag;int read(){ int x=0,f=0; char c=getchar(); while(c<'0'||c>'9'){ if(c=='-') f=1; c=getchar(); } while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return f?-x:x;}int main(){ n=read(); for(int i=1;i<=n;++i) A[i]=read(); //1.successive? flag=1; for(int i=2;i<=n;++i) if(A[i]!=A[i-1]+1){ flag=0; a=A[i-1],c=A[i],b=A[n]; break; } if(flag) T=A[n]+1; else{ L=a+1; //2.mL? flag=0; for(int i=c;i<=b;++i) if(i%L==0){ flag=1; m=i/L; break; } if(flag){ if(m>=2&&b-c+1>=L) T=(m-1)*L+b+1; else T=b+1; }else T=L; } cout<<T; return 0;}#include<bits/stdc++.h>using namespace std;int n,a[105],pi[1005];bool flag,ans[1005];int read(){ int x=0,f=0; char c=getchar(); while(c<'0'||c>'9'){ if(c=='-') f=1; c=getchar(); } while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return f?-x:x;}int main(){ n=read(); for(int i=1;i<=n;++i) a[i]=read(); for(int i=0;i<1000;++i){ flag=1,ans[i]=0; for(int j=1;j<=n;++j) if(i-a[j]==0||(i-a[j]>0&&ans[i-a[j]])) flag=0; if(flag) ans[i]=1; } //find circle for(int i=1;i<1000;++i){ int j=pi[i-1]; while(j>0&&ans[i]!=ans[j]) j=pi[j-1]; if(ans[i]==ans[j]) ++j; pi[i]=j; } cout<<1000-pi[999]; return 0;}#include<bits/stdc++.h>#include<random>using namespace std;int a,c,b,n,A[55];int main(){ default_random_engine e; uniform_int_distribution<int> u(1,20); e.seed(time(0)); a=u(e); c=u(e)+a; b=u(e)+c; for(int i=1;i<=b;++i) if(i<=a||(c<=i&&i<=b)) A[++n]=i; cout<<n<<"\n"; for(int i=1;i<=n;++i) cout<<A[i]<<" "; return 0;}#include<bits/stdc++.h>#include<windows.h>using namespace std;int main(){ int T=1000; while(T--){ printf("#%d\n",1000-T); system("random.exe > random.txt"); system("duipai.exe < random.txt > duipai.txt"); double st=clock(); system("answer.exe < random.txt > answer.txt"); double ed=clock(); if(system("fc duipai.txt answer.txt")) return(printf("Wrong Answer %.0lfms",ed-st))&&0; else if(ed-st>1000) return(printf("Time Limit Exceeded %.0lfms",ed-st))&&0; else printf("Accepted %.0lfms\n",ed-st); puts(""); } return 0;}结果
一开始,第 个比对出现差异,原因是当时 answer.cpp 第 行的 break 忘记加了。
更正后, 次答案均无差异。
因此,推断结论成立。
若后半段不是连续区间呢?
似乎没有确切规律,或者说规律太过复杂,有兴趣的读者可以自行探究。
补充
最后问了一下 这在博弈论里是什么问题,他是这么说的,仅供参考:
是的,这个问题属于组合博弈论(Combinatorial Game Theory)的范畴,具体是无偏博弈(impartial game)中的减法游戏(subtraction game),也可以看成是单堆 Nim 的变种。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!












