题目大意:
就是现在有一堆石子, 然后先手第一次取走任意数量(至少1颗, 不能取完), 然后双方轮流拿石子的时候, 拿走的数量不能超过上一次被拿走石子数的K倍, 拿走最后一颗石子的人获胜, 给出n, k, 求出先手是否必败, 如果必胜, 则输出第一次应该拿走的最少数量
大致思路:
首先可以参考2009年国家集训队冬令营论文 : 《从"k"倍动态减法游戏"出发探究一类组合游戏问题》
但是在那篇论文上给出的方法是O(n)处理出所有的f( i ), 然后对于每次询问O(1)回答的算法, 但是这样子在本题中是行不通的(n <= 1e8, 预处理出n是不可能的), 于是需要用到一个比较巧妙的想法, 构造数列的方法:
首先对于 k == 1的时候, 可以发现, 对于数n, 当n == 2^i时先手必败, 否则先手只需要拿走其二进制最后一个1即可, 后手一定无法取完, 但是当n == 2^i时, 先手一定会拿不完并且后手一定能拿到其二进制最后一个1, 反客为主
对于 k == 2, 就是斐波那契博弈的模型, 当n是斐波那契数的时候先手必败, 否则先手必胜, 因为根据齐肯多夫定理, 任意一个正整数可以表示成不相邻的斐波那契数列的和, 那么拿走最小的那一堆斐波那契数即可, 这样我们把多个斐波那契堆表示成1, 先手就拿走了最末的一个1, 后手一定拿不掉剩下的高位的1
于是对于k >= 3的话, 猜想是否也能找出相应的方法, 使得对于任意一个正整数n能表示成这个数列中不相邻的数的和, 且不相邻的两个数a[i], a[i + 2]满足a[i]*K < a[i + 2]
那么考虑下面这个构造方法:
需要构造出的数列是a[1~t], 用b[1~t]作为辅助数列, b[i]表示a[1~i]中若干个不相邻的数之和能表示出的最大值
那么a[i + 1] = b[i] + 1,(想染b[i] + 1不能用a[1~i]表示出来, 只能新建一项)
然后b[i + 1] = b[p] + a[i + 1], 其中p是最大的那个a[p]使得a[p]*K < a[i + 1]
那么b[i + 1] = b[p] + a[i + 1], p = max{p | a[p]*K < a[i + 1]}
当然当p不存在时, b[i + 1] = a[i + 1]了, 关于这个构造数列之后, 如果n在a数列中则是先手败, 否则先手胜, 第一步最少当然是取其唯一分解出的a[]数组的若干不相邻项的和中的最小项
代码如下:
Result : Accepted Memory : 7448 KB Time : 48 ms
/*
* Author: Gatevin
* Created Time: 2015/5/11 18:34:50
* File Name: Rin_Tohsaka.cpp
*/
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;
#define foreach(e, x) for(__typeof(x.begin()) e = x.begin(); e != x.end(); ++e)
#define SHOW_MEMORY(x) cout<<sizeof(x)/(1024*1024.)<<"MB"<<endl
/*
* 构造数列a[], b[]
* 其中a[i]表示构造出的数列
* b[i]表示a[1~i]中若干个两两不相邻的数能表示出的最大数
* 那么a[i + 1] = b[i] + 1
* b[i + 1] = b[t] + a[i + 1], t = max{t|a[t]*k < a[i + 1]}
*/
//刚开始数组开小了...
#define maxn 2000000
int a[maxn];
int b[maxn];
int main()
{
int t;
int n, k;
scanf("%d", &t);
for(int cas = 1; cas <= t; cas++)
{
scanf("%d %d", &n, &k);
printf("Case %d: ", cas);
int now = 1;
a[1] = 1;
b[1] = 1;
int last = 1;
for(int i = 2; b[now] <= n; i++)
{
a[i] = b[i - 1] + 1;
/*
for(int j = now; j > 0; j--)
{
if(a[j]*k < a[i])
{
b[i] = b[j] + a[i];
break;
}
else
b[i] = a[i];
}//这么写是超时的= =...脑抽了
*/
b[i] = a[i];
while(a[last + 1]*k < a[i])
last++;
if(a[last]*k < a[i]) b[i] = b[last] + a[i];
now++;
}
for(int i = 1; i <= now; i++)
if(a[i] == n)//P点, 先手败
{
puts("lose");
goto nex;
}
//否则先手胜, 拿走最小的那一份就是分成不相邻的多个a[i]的和中最小的那个a[i]即可
while(a[now] != n)
{
if(n > a[now]) n -= a[now];
now--;
}
printf("%d\n", a[now]);
nex:;
}
return 0;
}