谁拿最后一根火柴 程序设计 大家帮帮忙!

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了谁拿最后一根火柴 程序设计 大家帮帮忙!相关的知识,希望对你有一定的参考价值。

现有21根火柴,两人轮流取,每人每次可以取走1至4根,不可多取,也不能不取,谁取最后一根火柴谁输。请编写一个程序进行人机对弈,要求人先取,计算机后取;计算机一方为“常胜将军”。

[提示] 在计算机后走的情况下,要想使计算机成为“常胜将军”,必须找出取的关键。根据本题的要求加以总结出,后走一方取子的数量与对方刚才一步取子的数量之和等于某个数,就可以保证最后一个子是留给先取子的那个人的。 据此分析进行算法设计就是很简单的工作,编程实现也十分容易。

计算机编程,我想要一个完整的程序。最好是java的!但是你要是别的我也没关系,主要是能看懂就行。答对了我再给你50分,真的不骗你,我现在有很多分!
不一定非要这个题目的,类似的也可以的,主要是想明白这个道理!
我主要是想问问下面这道题,原以为这两道题,差不多,但是现在看来还是相差很远的。

有3堆火柴,一堆3根,一堆5根,一堆7根,两人轮流拿,规定一次只能从一堆里拿,根数不限。
问:如何设计算法才能使你拿不到最后一根?能用程序写出来吗?拜托了。我一定加分的。

三楼的同志,你写的代码,我执行了,但是不正确啊,我要是总输入2的话,他就一直执行下去,没有结束。你看你能不能改改,我的悬赏已经加了

这个道理和编程无关,每人最多取4根,
1+4=5
21=5*4+1
也就是说,只要保证每轮两方之和是5,那么4轮后取走20根,最后先取的人必定取最后一根。

第二题:需要用递推的方式,计算所有必胜必输的状态,然后保证每次取火柴都让对方到达必输状态。
所谓必输就是只剩最后一根,或者无论怎么取后的结果都是必胜。
所谓必胜,就是可以对方到达必输状态的情况。

程序如下:
import java.io.*;

public class Picker
// 火柴堆的输赢状况
private final static int EMPTY=0; // 这种排列不可能出现,如108
private final static int UNKNOWN=1; // 尚未计算出
private final static int WIN=2; //必胜
private final static int LOSE=3; //必输(如果对方够聪明)

// 用数组,保存每种火柴堆排列的输赢状态,下标为排列,如357, 111, 001, 100等等
private int[] status;

public Picker()
// 初始化状态数组,排除所有不可能出现的情况
status = new int[358]; // 0 - 357
int i,j,k;
for (i=0; i<=3; i++)
for (j=0; j<=5; j++)
for (k=0; k<=7; k++)
status[i*100+j*10+k] = UNKNOWN;




// 已知 100, 010, 001必输
status[1]=LOSE;
status[10]=LOSE;
status[100]=LOSE;

// 所有能使对方到达上述三个状态的排列都是必赢的
markWin(1);
markWin(10);
markWin(100);

// 递推计算,直至357的情况被计算出来
while (status[357] == UNKNOWN)
//找到第一个没有计算的
int node=2;
for (node = 2; node<357; node++)
if (status[node] == UNKNOWN) break;

// 它的所有下个状态肯定都是必赢,不然以前就能算出。
status[node] = LOSE;
// 所有能使对方到达这个状态的排列都是必赢的
markWin(node);



// 所有能使下个状态变必输的排列都是必赢的
private void markWin(int node)
// 假设node为必输
// 每堆的数量分别为i,j,k
int i = node / 100;
int j = node / 10 % 10;
int k = node % 10;
// 先是第一堆,可能为i+1, i+2, ..., 3
for (int i1 = i+1; i1 <= 3; i1++)
status[i1*100+j*10+k] = WIN;

// 第二堆
for (int j1 = j+1; j1 <= 5; j1++)
status[i*100+j1*10+k] = WIN;

// 第三堆
for (int k1 = k+1; k1 <= 7; k1++)
status[i*100+j*10+k1] = WIN;




// 查找所有可能的一次取火柴后的排列,找出其中必输的状态,把这个作为自己的走法
public int getWinPick(int node)
//每堆的数量分别为i,j,k
int i = node / 100;
int j = node / 10 % 10;
int k = node % 10;
// 先是第一堆,可能留下0, 1, ..., i-1
for (int i1 = 0; i1 < i; i1++)
if (status[i1*100+j*10+k]==LOSE) return i1*100+j*10+k;

// 第二堆
for (int j1 = 0; j1 < j; j1++)
if (status[i*100+j1*10+k]==LOSE) return i*100+j1*10+k;

// 第三堆
for (int k1 = 0; k1 < k; k1++)
if (status[i*100+j*10+k1]==LOSE) return i*100+j*10+k1;

// 没有找到,那么先顽强抵抗一下,只取一根
if (i>0) return (i-1)*100+j*10+k;
else if (j>0) return (j-1)*10+k;
else return k-1;


public static void main(String[] args) throws Exception
Picker picker = new Picker();
// 一开始的排列是357
int node = 357;

BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));

while (node > 0)
// 计算机先
System.out.print("Now is "+node);
node = picker.getWinPick(node);
System.out.println(", I pick to "+node);
if (node == 0)
System.out.println("I lose");
return;

// 对方再取
System.out.println("Now is your turn: ");
String input = stdin.readLine();
int node1 = 0;
try
node1 = Integer.parseInt(input);
catch (Exception e)
System.out.println("Invalid input, you lose");
break;

// 这里没有判断取的是否合法,即node和node1之间是否仅差一位数字
if ( node1==0 )
System.out.println("You lose");

node = node1;




这个程序只是例子,是说明算法,没有判断输入的合法性,所以不能一直输入2的,人嘛,自己也遵循一下游戏规则吧。
另外,稍做改动,如果不利,计算机不会马上认输了。
参考技术A 总结出,后走一方取子的数量与对方刚才一步取子的数量之和等于,就可以保证最后一个子是留给先取子的那个人的。
据此分析进行算法设计就是很简单的工作,编程实现也十分容易。

1+4=5
21=5*4+1
*程序与程序注释

#include<stdio.h>
void main()

int a=21,i;
printf("Game begin:\n");
while(a>0)

do
printf("How many stick do you wish to take(1~%d)?",a>4?4:a);
scanf("%d",&i);
while(i>4||i<1||i>a); /*接收正在确的输入*/
if(a-i>0) printf(" %d stick left in the pile.\n",a-i);
if((a-i)<=0)

printf(" You have taken the last stick.\n");
printf(" * * * You lose! \nGame Over.\n"); /*输出取胜标记*/
break;

else
printf(" Compute take %d stick.\n",5-i); /*输出计算机取的子数*/
a-=5;
printf(" %d stick left in the pile.\n",a);

参考技术B 用java编:
public class Tnum
public static void main(String args[])
int num=21;
int people,computer;
while(num>1)
people=(int)(Math.random()*4+1);
computer=5-people;
num=num-people-computer;
System.out.println("人取:"+people);
System.out.println("电脑取:"+computer);
System.out.println("剩:"+num);

System.out.println("最后剩:"+num+",人输!");

[CQOI2013]新Nim游戏

Description

传统的Nim游戏是这样的:有一些火柴堆,每堆都有若干根火柴(不同堆的火柴数量可以不同)。两个游戏者轮流操作,每次可以选一个火柴堆拿走若干根火柴。可以只拿一根,也可以拿走整堆火柴,但不能同时从超过一堆火柴中拿。拿走最后一根火柴的游戏者胜利。
本题的游戏稍微有些不同:在第一个回合中,第一个游戏者可以直接拿走若干个整堆的火柴。可以一堆都不拿,但不可以全部拿走。第二回合也一样,第二个游戏者也有这样一次机会。从第三个回合(又轮到第一个游戏者)开始,规则和Nim游戏一样。
如果你先拿,怎样才能保证获胜?如果可以获胜的话,还要让第一回合拿的火柴总数尽量小。
 

Input

第一行为整数k。即火柴堆数。第二行包含k个不超过109的正整数,即各堆的火柴个数。
 

Output

 
输出第一回合拿的火柴数目的最小值。如果不能保证取胜,输出-1。

Sample Input

6
5 5 6 6 5 5

Sample Output

21

HINT

k<=100

可知异或和为0则必败,也就是说开头取掉几堆后,剩余集合不能出现异或为0的子集

可知就是维护一个权值和最大的线性无关组(线性基)

从大到小排序,一个个加入线性基

如果没有成功插入,那么说明该元素与其他线性相关,即可以用线性基中的子集异或和表示

这和元素的贪心很像

给出拟阵证明

我们设n个火柴堆的数目为集合S,若某个S的子集r不存在任何一个非空子集异或和0,则r∈I.下面我们证明二元组M=(S,I)是一个拟阵。
遗传性:设A∈I,则A是S的线性无关组,则A的任意非空子集均线性无关,即对A的任意子集B,B均线性无关,因此B∈I,证毕。
交换性:设A,B∈I,且|A|<|B|,我们要证明存在x∈B,使得A∪{x}∈I.利用反证法,假设对于任意x∈B-A,均有A∪{x}不属于I,则B-A中的元素均在A的异或空间中,可由A的子集异或和表示。
因此B中的元素都在A的异或空间中。那么必然有B的异或空间包含于A的异或空间。由|A|<|B|且A,B线性无关,显然矛盾。因此交换性存在,证毕。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 using namespace std;
 7 typedef long long lol;
 8 int P[40],a[101],n;
 9 lol ans;
10 int add(int x)
11 {int i;
12   for (i=30;i>=0;i--)
13     if (x&(1<<i))
14       {
15     if (P[i]==0)
16       {
17         P[i]=x;
18         break;
19       }
20     x^=P[i];
21       }
22   return x;
23 }
24 int main()
25 {int i;
26   cin>>n;
27   for (i=1;i<=n;i++)
28     scanf("%d",&a[i]);
29   sort(a+1,a+n+1);
30   for (i=n;i>=1;i--)
31     {
32       if (add(a[i])==0) ans+=a[i];
33     }
34   cout<<ans;
35 }

 

以上是关于谁拿最后一根火柴 程序设计 大家帮帮忙!的主要内容,如果未能解决你的问题,请参考以下文章

现有21根火柴,两人轮流取,每人每次可以取1到4根,不可多取,也不能不取,谁取最后一根谁输。

5.14常胜将军

P1247 取火柴游戏

[CQOI2013]新Nim游戏

BZOJ3105-新Nim游戏

P1247 取火柴游戏