HDU 2486 (HDU 2580, POJ 3922) A simple stone game K倍动态减法游戏

46 篇文章 0 订阅
29 篇文章 0 订阅

题目大意:

就是现在有一堆石子, 然后先手第一次取走任意数量(至少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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值