[POJ3922]Now解题报告
来源:互联网 发布:vue.js chrome插件 编辑:程序博客网 时间:2024/06/14 01:46
【题目描述】
从过去回来后,你又对未来产生了浓厚兴趣。你去办理时空签证,却被告知能到未来
的人都必须是高智商的,所以需要玩一个游戏来测试智商。
游戏规则如下:
1. 有一堆石子数为 N 的石子堆,两个人轮流取,最后不能取的人输
2. 你是先手,且你不能在第一步将石子全部取完
3. 设上一个人选的石子数为 X,则这次取的石子数不能超过 X*K
你需要知道你是否能胜利,以及如何胜利
【输入格式】
第一行一个正整数 T,表示测试组数
下面的 T 行,每行两个整数,代表 N 和 K
【输出格式】
对于每组数据,输出组数,然后如果先手负,则输出”lose”,否则输出最小的第一步取的
石子数,注意中间有空格
【样例】
Sample Input
5
16 1
11 1
32 2
34 2
19 3
Sample Output
Case 1: lose
Case 2: 1
Case 3: 3
Case 4: lose
Case 5: 4
【备注】
对于 10%的数据,有 N <= 100, K <= 2
对于 30%的数据,有 N <= 10^8, K <= 3
对于 100%的数据,有 2 <= N <= 10^8, K <= 10^5, T <= 200
这个题是一道非常非常好的题,我能做到它真是太幸运了!
为了搞明白其正确性,我参阅了国家集训队论文,发现这道题从2002年便开始出现在集训队论文里,当时它的时间复杂度是
而作为一道神题的题解,网上的题解(估计都是抄的)都TM是一个模子,完全讲不明白。
我在这里给出一个较为严谨的思路和证明(这可是我花了一天时间才搞明白的)。
首先呢,化繁为简,考虑K=1的情况:没有思路?打表!
打出不同的N,给出的最终答案是多少。
发现当N=
然后呢,让我们考虑一下这是为什么。
这显然是与二进制有关的,于是我们转二进制考虑:
减一个数我们把它视为减去若干个
那么我们从高到低减去这些1,如果一个1对应的被减数位是1,那么就直接减掉好了;而如果一个1对应的被减数位是0,那么就相当于是把上一个1的位置变成0,而从那里开始到这一位都变成1.
也就是说,从0减不会让1减小,而取完所有石子的充要条件是石子数的二进制中的1的总数是0.
那么一件有趣的事情就出现了,既然是博弈,那么应该在每次A取完后,B不管如何取,都只能从0位上减;这显然是容易做到的,只要A每次取lowbit即可,而B所减的最低位一定会出现一个1,所以A一定能取到lowbit。而之所以
而打表发现,确实是lowbit,也就证实了我们的结论。
考虑我们是如果搞出K=1的情况的,
1、打表。(不要小看打表,这是一个非常非常重要的技巧!)
2、证明出四件事:
①依照某种取法,A每次都能取一个值;而这么做将剩给B一个他不能取到的数,或A把石子全部取走了。
②这个值只要石子数为正,都一定存在。
③这种取法是唯一的。(这虽然是一个看上去没有关系的问题,但是在之后的证明中你会发现这才是这个题真正的重点)
④A每次一定能取到这个值。
然后我们开始考虑K=2的情况。
打表发现,必败态是Fibonacci。
下面我们将说明这个问题。
(在以下的叙述中,我们将认为斐波那契数列以1为首项,2为次项)
先介绍——齐肯多夫引理。
任意一个正整数必然能被表示成斐波那契数列中的若干不连续项(包括一项)之和;
这个定理看起来似乎非常玄妙,但实际上它不过是两个简单事实的叠加而已;虽然有的时候形式上的证明是必不可少的,但是我却认为非形式的证明才能揭示定理的灵魂所在,大段的符号和逻辑转移往往最关键的突破被掩盖。
显然1,2(斐波那契数列中的前两项)可以被用斐波那契数列中的项表示,那么我们不妨设1..i-1,均能用斐波那契数列中的若干项表示;
设
若
若
综上,原命题成立。
齐肯多夫定理说明了①②,而实际上③④正是齐肯多夫定理的推论。
若干不相邻项实际上就是说任意两项之比大于2,因为
也就是说我们可以把一个不在斐波那契数列上的整数写成斐波那契二进制=齐肯多夫拆分的形式,那么发生的事情就像K=1一样了;我们只需要取出其所有齐肯多夫拆分中的最小最小项即可。而下面我们将说明实际上,任意整数的齐肯多夫拆分都是唯一的。
③对于i,我们试图将其用斐波那契数列中的数分解,并依据不能选连续项的原则,那么显然,若i只能用
若i不选
齐肯多夫分解唯一。
④(这个命题对于斐波那契数列来说有多种证明方式,但是这里我们介绍一种易于向高维推广的)
我们考虑A与B博弈,A必胜的时候,在某一轮中,B面对y,y的齐肯多夫拆分最小项为
而若
然后我们开始试图向高维推广齐肯多夫定理及其推论。
考虑齐肯多夫定理的证明过程,显然我们只需要把
而我们很容易就可以发现,上述四点的证明甚至几乎不用有太大的变化,便可完全套用在新的数列上;而且足够严谨。
最后我们分析一下时间复杂度的问题,这几乎已经是显然的了。
显然从前一项到后一项,至少要乘
所以最后的时间复杂度是
不过。。根据出题人懒惰定律,出题人有相当大地可能性会出随机数据。而如果是随机数据的话,可以期望去掉一个2,0.5s差不多是可以的。。
而事实,也确实是这样。。
还有一种推数列的方法是搞两个数组,实际上与这种递推是可以互相推出的,写起来还要麻烦一点;这里就不再赘述了。
Code:
#include<cstdio>int A[1000000];inline int in(){ char c=getchar(); int x=0; while(c<'0'||c>'9')c=getchar(); for(;c>='0'&&c<='9';c=getchar())x=x*10+c-'0'; return x;}int main(){ int i,N,K,tot,p; A[1]=1; for(int T=in(),t=1;t<=T;++t){ N=in(),K=in(); for(tot=2,p=1;A[tot]<=N;){ while((long long)A[p]*K<A[tot])++p; A[tot]=A[tot++]+A[p]; } printf("Case %d: ",t); if(A[--tot]==N)puts("lose"); else{ while(N!=A[tot]){ N-=A[tot]; while(N<A[tot])--tot; } printf("%d\n",N); } }}
这道题给我的收获有:
①对于奇怪的DP、博弈论什么的,打表寻找规律,再试图证明规律,便很容易向高维推广了。
②DP在证明中的应用(DP->数学归纳法):证明第一项成立;证明若前n项成立,则下一项也成立。
③复杂的问题往往看起来难以下手,但实际上如果我们先考虑一些简单的情况,即使是再复杂的问题便也会迎刃而解了。
- [POJ3922]Now解题报告
- 解题报告
- 解题报告
- 解题报告
- 解题报告
- 解题报告
- 解题报告
- 解题报告
- Antiprime解题报告
- expr解题报告
- 华容道解题报告
- tju解题报告
- zju1062/pku1095解题报告
- UsacoGate解题报告 --- 序曲
- ZJU 2060 解题报告
- ZJU 1331 解题报告
- ZJU 1115 解题报告
- ZJU1057解题报告
- 《精通Linux设备驱动程序开发》——块设备驱动程序
- 【反汇编分析】函数栈帧
- 柴郡猫技术--C++中的PIMPL设计模式
- 第三周项目 2 测试与设计
- 【leetcode】Merge k Sorted Lists
- [POJ3922]Now解题报告
- Annotation笔记
- BZOJ 2152 (树形DP)
- oc中不允许方法重载
- 逆序对的求法 归并排序
- Codeforces 296B Yaroslav and Two Strings dp+容斥(入门
- Python函数小结(2)-- 装饰器、 lambda
- android 常用URI 值得记住
- 【leetcode】Valid Parentheses