《ACM国际大学生程序设计竞赛题解I》——6.10
Pku 1143:
Description
- 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
Output
题目大意:两个人玩零和博弈游戏,轮流选取[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的更多相关文章
- 《ACM国际大学生程序设计竞赛题解Ⅰ》——基础编程题
这个专栏开始介绍一些<ACM国际大学生程序设计竞赛题解>上的竞赛题目,读者可以配合zju/poj/uva的在线测评系统提交代码(今天zoj貌似崩了). 其实看书名也能看出来这本书的思路,就 ...
- 《ACM国际大学生程序设计竞赛题解I》——6.11
pku 1107: Description Weird Wally's Wireless Widgets, Inc. manufactures an eclectic assortment of sm ...
- 《ACM国际大学生程序设计竞赛题解I》——6.8
Poj1068: Description Let S = s1 s2...s2n be a well-formed string of parentheses. S can be encoded in ...
- 《ACM国际大学生程序设计竞赛题解Ⅰ》——模拟题
这篇文章来介绍一些模拟题,即一类按照题目要求将现实的操作转换成程序语言. zoj1003: On every June 1st, the Children's Day, there will be a ...
- 2018 ACM 国际大学生程序设计竞赛上海大都会部分题解
题目链接 2018 ACM 国际大学生程序设计竞赛上海大都会 下午午休起床被同学叫去打比赛233 然后已经过了2.5h了 先挑过得多的做了 .... A题 rand x*n 次点,每次judge一个点 ...
- 2018 ACM 国际大学生程序设计竞赛上海大都会赛
传送门:2018 ACM 国际大学生程序设计竞赛上海大都会赛 2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛2018-08-05 12:00:00 至 2018-08-05 17:00:0 ...
- 2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 F Color it
链接:https://www.nowcoder.com/acm/contest/163/F 来源:牛客网 2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 F Color it 时间限制:C ...
- 2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 F Color it (扫描线)
2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 F Color it (扫描线) 链接:https://ac.nowcoder.com/acm/contest/163/F来源:牛客网 时间 ...
- 2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 J Beautiful Numbers (数位DP)
2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 J Beautiful Numbers (数位DP) 链接:https://ac.nowcoder.com/acm/contest/163/ ...
随机推荐
- ArcMap - 分割.
一,分割面: 1,在屏幕上新增线分割面: 使待编辑的面处于编辑状态 -> 选择待分割的面(使其处于选中状态) -> 选择编辑工具的 (Cut Polygons Tools) ->画线 ...
- ASP.NET Web API 文件產生器 - 使用 Swagger
转帖:http://kevintsengtw.blogspot.hk/2015/12/aspnet-web-api-swagger.html Swagger 是一套 API 互動文件產生器,使用 HT ...
- 关于SQL配置管理器的服务无法启动的解决办法!
由于各种问题的因素,导致SQL服务无法启动,然后去事件查看器里看了下,有两个关于SQL 的错误.分别是实例中master.mdf和master.ldf的文件系统拒绝访问! 为了赶作业,带着焦急的心情去 ...
- 【POJ1568】【极大极小搜索+alpha-beta剪枝】Find the Winning Move
Description 4x4 tic-tac-toe is played on a board with four rows (numbered 0 to 3 from top to bottom) ...
- javascript--烟火效果
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <meta nam ...
- T-SQL语言基础
1.T-SQL语言 CREATE:创建新对象,包括数据库.表.视图.过程.触发器和函数等常见数据库对象. ALTER:修改已有对象的结构. DROP:用来删除已有的对象.有些对象是无法删除的,因为它们 ...
- Python直接迭代序列比通过索引迭代序列快。
小脚本跑一下看看时间. 原理:直接迭代序列是通过Python内置的迭代器去实现的,而如果迭代序列需要先造一个可迭代的序列出来.内置的迭代器并不是一下将所有的数据放入内存中,而是需要多少取多少. #!/ ...
- java中如何操作数据库(增删改查)
EntityManager 是用来对实体Bean 进行操作的辅助类.他可以用来产生/删除持久化的实体Bean,通过主键查找实体bean,也可以通过EJB3 QL 语言查找满足条件的实体Bean.实体B ...
- Tomcat虚拟主机配置
3.1.配置虚拟主机 配置虚似主机就是配置一个网站. 在Tomcat服务器配置一个虚拟主机(网站),需要修改conf文件夹下的server.xml这个配置文件,使用Host元素进行配置,打开serve ...
- 提供进销存、ERP系统快速开发框架源码 (C#+SQL)
C/S系统开发框架-企业版 V4.0 (Enterprise Edition) 简介: http://www.csframework.com/cs-framework-4.0.htm 视频下载: 百度 ...