Pku 1143:

Description

Christine and Matt are playing an exciting game they just invented: the Number Game. The rules of this game are as follows. The players take turns choosing integers greater than 1. First, Christine chooses a number, then Matt chooses a number, then Christine again, and so on. The following rules restrict how new numbers may be chosen by the two players:

  • A number which has already been selected by Christine or Matt, or a multiple of such a number,cannot be chosen.
  • A sum of such multiples cannot be chosen, either.

If a player cannot choose any new number according to these rules, then that player loses the game. Here is an example: Christine starts by choosing 4. This prevents Matt from choosing 4, 8, 12, etc.Let's assume that his move is 3. Now the numbers 3, 6, 9, etc. are excluded, too; furthermore, numbers like: 7 = 3+4;10 = 2*3+4;11 = 3+2*4;13 = 3*3+4;... are also not available. So, in fact, the only numbers left are 2 and 5. Christine now selects 2. Since 5=2+3 is now forbidden, she wins because there is no number left for Matt to choose. Your task is to write a program which will help play (and win!) the Number Game. Of course, there might be an infinite number of choices for a player, so it may not be easy to find the best move among these possibilities. But after playing for some time, the number of remaining choices becomes finite, and that is the point where your program can help. Given a game position (a list of numbers which are not yet forbidden), your program should output all winning moves. A winning move is a move by which the player who is about to move can force a win, no matter what the other player will do afterwards. More formally, a winning move can be defined as follows.

  • A winning move is a move after which the game position is a losing position.
  • A winning position is a position in which a winning move exists. A losing position is a position in which no winning move exists.
  • In particular, the position in which all numbers are forbidden is a losing position. (This makes sense since the player who would have to move in that case loses the game.)

Input

The input consists of several test cases. Each test case is given by exactly one line describing one position. Each line will start with a number n (1 <= n <= 20), the number of integers which are still available. The remainder of this line contains the list of these numbers a1;...;an(2 <= ai <= 20). The positions described in this way will always be positions which can really occur in the actual Number Game. For example, if 3 is not in the list of allowed numbers, 6 is not in the list, either. At the end of the input, there will be a line containing only a zero (instead of n); this line should not be processed.

Output

For each test case, your program should output "Test case #m", where m is the number of the test case (starting with 1). Follow this by either "There's no winning move." if this is true for the position described in the input file, or "The winning moves are: w1 w2 ... wk" where the wi are all winning moves in this position, satisfying wi < wi+1 for 1 <= i < k. After this line, output a blank line.

题目大意:两个人玩零和博弈游戏,轮流选取[2,20]的整数,最终没有数字可选的玩家输。而数字无法被选有如下的两条限制:

(1)    选过的数字不能再选,其整数倍的数字也不能再选。

(2)    不能选的数字的和不能被选择。

现在给出游戏过程中某个某个状态<i,j,k…>表示当前i、j、k…还可以选,那么请编程输出当前状态下的所有必胜策略。如果不存在,则输出对应的语句。

分析:其实这道题和之前我们探讨过的一道博弈问题非常的类似,可以说在整体的思维上是一致的。我们从特殊的情形来分析然后逐步进行推广。如果给出的状态是<2,4>,那么显然必胜数(就是当前状态游戏选择该数会引导出必胜策略,下面都简称为必胜数)是2。如果给出的状态是<2,3,4>,显然必胜数是4。很容易看到,对于小规模的状态时,我们非常容易判断,那么对于较大规模的状态,我们依据前后状态的转移关系(一个是状态参数关系的转移,另一个则是博弈NP态的转移),可以通过递归缩减状态规模的方法来判断当前状态是否存在必胜态。

以上是对于这道问题一个整体思维的分析,下面我们面临的问题是如何编程来模拟实现这个基于递归的计算过程。

数据结构:

首先我们需要一种结构来记录某种状态<i,j,k…>,为了编程的方便,我们用一个整数num来记录,但是我们从二进制的角度去解读这个整数num,举个例子来说,对于状态<2,3,4>,即表示二进制数从右边开始,第2、3、4位是1,即有num = 14.

其次我们看到这个递归搜索的过程可能在后面输入的时候进行重复计算,因此这里应该采用记忆化搜索的策略,这里我们用map[num]来表示状态num(基于上一段我们说过的转化)的胜负态。

状态转移:

状态参数之间的转移:假设这里我们给出了一个状态num0,<a1,a2,a3…>,现在我们需要做的是依次取走a1、a2、a3…所形成的状态num1、num2、num3中的胜负态是怎样的。那么这里我们面临这样一个问题,从num0出发,我们如何得到num1、num2、num3这些状态参数呢?我们当然要从题目要求当中提取信息——取走a1会导致a1的整数倍、a1和其余不可取的数字的和变成不可取,这便是状态转移的关键。

博弈NP态的转移:这一点是非常重要的,因为我们最终要看的就是这种状态到底存不存在必胜策略,这里我们需要知道的是,从一个大规模问题递归搜索胜负态的时候,对于某条状态路径num0->num1->num2…,其胜负态一定是交替的,即N->P->N->P…这一点基本对于所有的二元零和博弈都适用。

进一步基于编程化的思考:

那么通过上面的分析,我们看到一个整体渐渐清晰的思维轮廓,基于状态参数之间的转移关系和二元博弈自身胜负态的转移关系,我们能够实现递归求解,但是在具体的编程中,我们应该如何实现呢?

一个最困难的点就是状态参数之间的转移,这里我们要运用到巧妙的位运算,首先假设一个问题情境,对于状态num0,我们拿走整数i,将生成的状态用num1记录。我们用j记录i的整数倍,那么num0 | 1 << j所表示的二进制数将能够表征状态num1所有不能取的数字,即如果数字k在状态num1中不能取,则num1的二进制表示中自右往左第k+1位是0.下面我们要做的应该是保留那些能够取的数字,我们通过一个整数2^(j-1) – 1来得到状态num0中小于j的数字,然后二者做‘|’运算,在于num0做‘&’运算,即可得到num1.

即num1 = num0 & ((num0 | 1 << j)| 2^(j-1) - 1),这是这道题的状态参数转移式字。

而对于NP态的转移方程,设map[num]记录状态num的胜负态(map[num] = 1表示面临当前状态的玩家有必胜态,否则map[num] = 0),num1,num2,num3…numi是num0的下一个状态,则有如下的胜负态转移方程:

map[num0] = (1 - map[num1]) | (1 – map[num2]) |(1 – map[num3] ) | … |(1 – map[numi]).

基于这个层面的分析,便可以编程实现了。

简单的参考代码如下:

#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = ;//总状态数 int Map[maxn];
int mask[];
int judge(int num)
{
int i , j;
if(Map[num] == -)
{
Map[num] = ;
for(i = ;i <= ;i++)//遍历当前状态可能取走数的所有情况
{
if((num & mask[i]))//整数i在当前状态可以取
{
int temp = num;
j = i;
while(j <= )
{
temp &= (((num | ) <<j) | (mask[j] - )); //位运算,<<优先于 | ,这里应该加小括号
j += i;
} int Win = - judge(temp);
if(Win == )
Map[num] += mask[i];
}
}
} return Map[num];
} int main()
{
mask[] = ;
Map[] = ;
for(int i = ;i < ;i++)
mask[i + ] = << i;
memset(Map,-,sizeof(Map));
int n;
int tt = ;
while(scanf("%d",&n) != EOF)
{
if(n == ) break; int a = ;
for(int i = ;i <= n;i++)
{
int temp;
scanf("%d",&temp);
a |= mask[temp];//对于初始状态的重构
}
printf("Test Case #%d\n",tt++);
int answer = judge(a);
if(answer == )
printf("There's no winning move.\n");
else
{
printf("The winning moves are:");
answer /= ;
for(int i = ;i <= ;i++)
{
if(answer% == )
printf(" %d",i); answer >>= ;
if(answer == )
break;
}
printf("\n");
}
printf("\n");
}
}

《ACM国际大学生程序设计竞赛题解I》——6.10的更多相关文章

  1. 《ACM国际大学生程序设计竞赛题解Ⅰ》——基础编程题

    这个专栏开始介绍一些<ACM国际大学生程序设计竞赛题解>上的竞赛题目,读者可以配合zju/poj/uva的在线测评系统提交代码(今天zoj貌似崩了). 其实看书名也能看出来这本书的思路,就 ...

  2. 《ACM国际大学生程序设计竞赛题解I》——6.11

    pku 1107: Description Weird Wally's Wireless Widgets, Inc. manufactures an eclectic assortment of sm ...

  3. 《ACM国际大学生程序设计竞赛题解I》——6.8

    Poj1068: Description Let S = s1 s2...s2n be a well-formed string of parentheses. S can be encoded in ...

  4. 《ACM国际大学生程序设计竞赛题解Ⅰ》——模拟题

    这篇文章来介绍一些模拟题,即一类按照题目要求将现实的操作转换成程序语言. zoj1003: On every June 1st, the Children's Day, there will be a ...

  5. 2018 ACM 国际大学生程序设计竞赛上海大都会部分题解

    题目链接 2018 ACM 国际大学生程序设计竞赛上海大都会 下午午休起床被同学叫去打比赛233 然后已经过了2.5h了 先挑过得多的做了 .... A题 rand x*n 次点,每次judge一个点 ...

  6. 2018 ACM 国际大学生程序设计竞赛上海大都会赛

    传送门:2018 ACM 国际大学生程序设计竞赛上海大都会赛 2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛2018-08-05 12:00:00 至 2018-08-05 17:00:0 ...

  7. 2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 F Color it

    链接:https://www.nowcoder.com/acm/contest/163/F 来源:牛客网 2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 F Color it 时间限制:C ...

  8. 2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 F Color it (扫描线)

    2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 F Color it (扫描线) 链接:https://ac.nowcoder.com/acm/contest/163/F来源:牛客网 时间 ...

  9. 2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 J Beautiful Numbers (数位DP)

    2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 J Beautiful Numbers (数位DP) 链接:https://ac.nowcoder.com/acm/contest/163/ ...

随机推荐

  1. MVC+EF 的增删改查操作

    1. //创建EF映射对象数据集 static Models.db_JiaoYouEntities DbDeleteData = new Models.db_JiaoYouEntities(); 2. ...

  2. UTF-8和GBK有什么区别?

    字符均使用双字节来表示,只不过为区分中文,将其最高位都定成1. 至于UTF-8编码则是用以解决国际上字符的一种多字节编码,它对英文使用8位(即一个字节),中文使用24位(三个字节)来编码.对于英文字符 ...

  3. linux常用命令之tail

    从指定点开始将文件写到标准输出.使用tail命令的-f选项可以方便的查阅正在改变的日志文件,tail -f filename会把filename里最尾部的内容显示在屏幕上,并且不但刷新,使你看到最新的 ...

  4. SGU 249.Matrix(Gray码)

    题意: 用0到2^(n+m-1)这2^(n+m-1)个数填在一个2^n*2^m的矩阵里,使得所有相邻的数的二进制表示只有一位不同. Solution: Gray码.对于第i行第j列的数,由i的Gray ...

  5. ThinkPHP框架下,给jq动态添加的标签添加点击事件移除标签

    jq移除标签主要就是$("#要移除的id").remove();不再赘述,这里要提醒的是jq中动态添加标签后怎样添加点击事件.一般的jq添加点击事件是用这种方法$("#i ...

  6. 原生Ajax + Promise

    有原生写的ajax + promise嫁接下 ;(function(root){ var LD = function(obj){ if( obj instanceof LD ) return obj; ...

  7. Ajax实现的长轮询不阻塞同一时间内页面的其他Ajax请求(同域请求)

    最近要做一个来电的弹屏功能,利用OM 系统的接口,OM系统发送请求到接口程序,分析数据添加到mysql数据库中,然后把最新的数据id 跟今日来电的总的数量存储到memcache 中.弹屏程序根据读取的 ...

  8. 解决nginx: [error] open() "/usr/local/nginx/logs/nginx.pid" failed错误

    重新启动服务器,访问web服务发现无法浏览,登陆服务器之 后进到nginx使用./nginx -s reload重新读取配置文件,发现报nginx: [error] open() "/usr ...

  9. 模块化编程AMD&CommonJS

    为什么要模块化编程 如果JS也可以像类似python,Java使用import,引入我们想要的模块,想要什么模块,就加载什么模块,可以给前端编程带来更多的便捷,结构更加清晰明了.但是,这样做有一个前提 ...

  10. Tag Helpers 介绍

    Tag Helpers 介绍 原文:Introduction to Tag Helpers作者:Rick Anderson翻译:刘浩杨校对:高嵩(Jack) 什么是 Tag Helpers? Tag ...