C语言取花生米(取棋子)系列问题。从一堆中轮流取的情况。

来源:互联网 发布:sql server卸载干净 编辑:程序博客网 时间:2024/06/11 17:48

最简单情况

描述
Tom和Jerry是邻居,他们都喜欢吃花生米。Tom的信条是“规则永远由强者制定,弱者只有遵守的权力”;Jerry则深信“头脑比拳头更有力量”。除此之外,他们都很聪明,恩,至少有211工程大学本科生水平。
五一长假第一天,Tom和Jerry在仓库散步的时候发现了一堆花生米(共n粒,n大于零小于等于1000)。Tom制定分花生米规则如下:
1、Tom和Jerry轮流从堆中取出k粒花生米吃掉,k大于零小于10;
2、为显示规则的公平性,Jerry可以选择先取或者后取。
根据定理“最后一粒花生米是苦的”,Jerry希望最后一粒花生米被Tom吃掉。请计算,Jerry为了达到目的应该先取还是后取,如果先取的话第一次应该取几粒。
输入
本题有多个测例,每个测例的输入是一个整数n,代表花生米的数量。
n等于0表示输入结束,不需要处理。
输出
每个测例在单独的一行内输出一个整数,Jerry先取花生米的粒数。
如果Jerry决定让Tom先取,输出0。
输入样例
1
2
3
0
输出样例
0
1
2

#include<stdio.h>int game(int n);int main(){    int a[1000],i=0,num;    do{        i++;        scanf("%d",&a[i]);    }while(a[i]!=0);    num=i-1;    for(i=1;i<=num;i++)    {        printf("%d\n",game(a[i]));    }}int game(int n){    int g[1000]={0},i,j;//0为先取必输;    for(i=2;i<=n;i++)    {        for(j=i-1;j>0&&j>i-10;j--)//注意花生米数量可能不足十个。小心数组越界。        {            if(g[j]==0)            {                g[i]=1;                break;            }         }    }    if(g[n]==0) return 0;    if(g[n]==1)    {        i=n;        while(1)        {            i--;            if(g[i]==0) return (n-i);        }    }}

稍微升级的情况

描述
五一长假第二天,Tom和Jerry在仓库散步的时候又发现了一堆花生米(这个仓库还真奇怪)。这次Tom制定分花生米规则如下:
1、Tom和Jerry轮流从堆中取出k粒花生米吃掉,k可以是1,5,10中的任意一个数字;
2、为显示规则的公平性,Jerry可以选择先取或者后取。
Jerry当然还是希望最后一粒花生米被Tom吃掉。请计算,Jerry为了达到目的应该先取还是后取。
输入
本题有多个测例,每个测例的输入是一个整数n,n大于零小于等于1000,代表花生米的数量。
n等于0表示输入结束,不需要处理。
输出
每个测例在单独的一行内输出一个整数:Jerry先取输出1;Tom先取输出0。
输入样例
1
2
3
4
0
输出样例
0
1
0
1

#include<stdio.h>//先取必胜输出1,后取必胜输出0; int game(int t);int main(){    int t[10000],i=0;    do{        i++;        scanf("%d",&t[i]);    } while(t[i]!=0);    int num=i-1;    for(i=1;i<=num;i++)    {        printf("%d\n",game(t[i]));    }}int game(int t){    int g[10000]={0},i=1;    for(i=2;i<=t;i++)    {        if(g[i-1]==0&&i-1>=1)        {            g[i]=1;            continue;        }        if(g[i-5]==0&&i-5>=1)        {            g[i]=1;            continue;        }        if(g[i-10]==0&&i-10>=1)        {            g[i]=1;            continue;        }    }    return g[t];}

加强版情况

描述
五一长假第三天,Tom和Jerry在仓库散步的时候又发现了一堆花生米(仓库,又见仓库……)。这次Tom制定分花生米规则如下:
1、Tom和Jerry轮流从堆中取出k粒花生米吃掉;
2、第一次取花生米的人只能取一粒,以后取花生米的数量不能超过前一个人取花生米数量的两倍;
3、为显示规则的公平性,Jerry可以选择先取或者后取。
Jerry当然还是希望最后一粒花生米被Tom吃掉。请计算,Jerry为了达到目的应该先取还是后取。
输入
本题有多个测例,每个测例的输入是一个整数n,n大于零小于等于1000,代表花生米的数量。
n等于0表示输入结束,不需要处理。
输出
每个测例在单独的一行内输出一个整数:Jerry先取输出1;Tom先取输出0。
输入样例
1
2
3
4
5
0
输出样例
0
1
0
0
1

#include<stdio.h>int g[10000][10000]={0},p[10000],t[10000];//较大数组声明全局变量int game(int t); int main(){    int i=0;    do{        i++;        scanf("%d",&t[i]);    } while(t[i]!=0);    int num=i-1;    for(i=1;i<=num;i++)    {        printf("%d\n",game(t[i]));    }}int game(int t){    int i,j,k;    p[1]=100000;//p[1]不存在,则设为很大的数。    for(i=2;i<=t;i++)//第一重循环,逆推堆中所剩花生米数量    {        j=0;        while(g[i][j]!=1)//第二重循环,循环到出现第一个g[i][j]=1时结束。这一行之后所有g[i][j]都等于1。        {            j++;            for(k=1;k<=j;k++)//第三重循环,讨论实际取了多少粒花生(如j=6最多可以取六粒花生,k=3实际只取了3粒)            {                if(2*j>=p[i-k])//所以g[i][2*j]=1                {                    g[i][j]=0;                    continue;//这一种取法必输,继续循环。讨论下一种可能。                }                if(g[i-k][2*j]==0)                {                    g[i][j]=1;                    break;//只要有一种取法能使下一个取的人必输就退出循环。                }            }        }        p[i]=j;//记录每一行出现第一个j的值,只要大于这个值g[i][j]=1;    }    return g[t][1];}

思考:
最基本的思路?
想确定有n个花生米的情况必须先确定有n-1,n-2……个花生米的情况,难以直接判断。而有1个花生米的情况却可以确定。所以从n=1时开始往2,3……逆推。
为什么要定义二维数组?
每次能取的花生米数量和堆中所剩花生米数量是要讨论的最关键的两个变量。在前两道题中,每次能取的花生米的数量是确定的,不需要讨论;堆中所剩花生米数量不能确定,所以需要建立一维数组来逆推。而在这道题中,这两个量都无法确定,所以需要建立二维数组和三重循环分别讨论、逆推。
如何限制每次能取的花生米数量?
在最外层的循环中,堆中所剩花生米数量i在0到输入的测试数据n之间。在第二层循环中,该如何确定每次能取得花生米数量j的范围呢?不难发现,一旦出现g[a][b]=1(先取必胜),那么对于所有j>=b,g[a][j]=1。所以第二层循环应该到找到第一个g[i][j]=1时停止,并且记录此时j的值。

加强版情况的变形

把加强版情况改为取到最后一颗花生米的人胜。这种情况下其实整体思路没有变,只是第二重循环中J的限制条件变了。不难发现,只要能取的花生米数比所剩花生米数大,也就是j>=i,一次取完所有花生米就可以获胜。所以只需要循环到j=i-1时就可以停止了。

#include<stdio.h>int n,l[100][100]={0};int main(){    int i,j,k,flag;    scanf("%d",&n);    l[1][1]=1;    for(i=2;i<=n;i++)//第一重循环,逆推堆中所剩花生米数量     {        for(j=1;j<=i-1;j++)//第二重循环,讨论能取多少花生米。        //因为j>=i 时先取必胜(l[i][j]=1),所以只需要讨论j<=i-1的情况。         {            flag=1;            for(k=1;k<=j;k++)//第三重循环 ,讨论实际取了多少花生米。             {                if(2*k>=i-k)// 下一个人取花生米的所有情况都为必胜,则先取必输。                 {                    break;                }                 if(l[i-k][2*k]==0)//下一个人取花生米存在必输的情况,则先取必胜。                 {                    flag=0;                    break;                }             }            l[i][j]=1-flag;        }    }    printf("%d",l[n][1]);}
原创粉丝点击