1. 程式人生 > >C語言取花生米(取棋子)系列問題。從一堆中輪流取的情況。

C語言取花生米(取棋子)系列問題。從一堆中輪流取的情況。

最簡單情況

描述
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]);
}