专注收集记录技术开发学习笔记、技术难点、解决方案
网站信息搜索 >> 请输入关键词:
您当前的位置: 首页 > C语言

巧取双数 求解思路

发布时间:2011-06-28 13:36:35 文章来源:www.iduyao.cn 采编人员:星星草
巧取偶数 求解思路
桌上25个棋子,游戏双方轮流取子,每人每次可取1~3颗,直到取完。取到偶数方的为获胜者
以上是题目,请问有什么取子策略始终保证某一方始终为胜者吗?

------解决方案--------------------
以前看过一个类似的题目:那个是要求取到最后一个字。昨天试了试,取胜的总的策略就是:要使你拿字后剩下的必须是偶数。例如:
如果你首先拿:就拿奇数,那么桌子上剩余就是偶数。这时候看对方拿,如果他拿奇数,那么你也奇数。剩余还是偶数;如果他偶数,你也就偶数,剩余的还是偶数。一直下去,必定是第一个拿的赢。昨天为了做这题目,拿了25颗围棋字在寝室和同学抓了半天,冲这一点,lz要多给点分啊。。^__^
------解决方案--------------------
原理:状态回溯(从最简单状态递推到复杂状态)
问题描述:可以用一个三元组完整的描述当前状态,定义三元组
 {剩余石子个数,甲的状态,乙的状态}。下面看看需要满足什么约束条件以及描述哪些状态。

 剩余石子+甲的石子+乙的石子=25(奇数);
 当前状态,甲先抓,乙后抓 或 乙先甲后;
 显然,当剩余石子是偶数时,(甲+乙)的石子应该是奇数;反之应是偶数。
 
 由此,可定义具体的状态集合:
1) 剩余偶数个石子 {11, 00}, {10, 01}, {00, 11}, {01, 10}
说明:“偶”表示当前剩余石子数为偶数,第一个状态{11, 00}中,‘11’为甲的状态,高位‘1’表示当前该甲先抓,低位‘1’表示当前甲所拥有的石子数为奇数,‘00’为乙

的当前状态,高位‘0’表示乙后抓,低位‘0’表示乙有偶数个石子。

同理,剩余奇数个石子时,当前状态集合为
2) 剩余奇数个石子 {00, 10}, {01, 11}, {10, 00}, {11, 01} 
 所以,以上两个状态集合可以描述问题的所有状态
 
 下面,考虑一种简单状态,即: 剩余石子数较少+甲必赢
若当前剩余石子数为2或3时,先抓者赢,对于甲来说,就是(如下)状态:
2 {11, 00}, {10, 01}
3 {10, 00}, {11, 01}

不难发现,若当前剩余石子数为4时,
若甲的石子数为奇数,而当前该乙抓,则甲有必赢策略。原因如下:
乙抓3个,甲抓1,甲赢;
乙抓2个,甲抓1,甲赢;
乙抓1个,甲抓3,甲赢。
若甲的石子数为奇数,而该甲抓,则甲亦必赢,因为甲可以直接抓三个。
用三元状态组描述剩余4个石子时,甲的必赢状态为:
4 {01, 10}, {11, 00}

因为一次最多抓三个,考虑当剩余5个石子时,若甲要必赢,必须保证抓完石子后的状态落在2、3、4的必赢状态中,这意味着可以从2、3、4的必赢状态反推至剩余5个石子的情况


状态迁移示例如下:
设当前状态是 5 {10, 00},即 该甲抓且甲的石子数为偶数。若甲抓1个石子,则状态迁移至 4 {01, 10},在4的必赢态中。显然 5 {10, 00} 也是甲的必赢状态,反之,5 {00, 

10} 是甲的必输状态,因为这是乙的必赢状态。综上所述,其他状态以此类推。

所以,使用递推的方法,通过三个初始的必赢状态(即,当前剩余石子数为2, 3, 4时),可以推至有更多石子时的必赢状态。

下面程序的初始状态是从6, 7, 8开始,具体代码如下:
C/C++ code

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    const int se[4][2]= {{3,0}, {2,1}, {0,3}, {1,2}};
    const int so[4][2]= {{2,0}, {3,1}, {0,2}, {1,3}};
    const int *sp;

    typedef struct
    {
        int num;
        const int *spar[2];
    } state;

    state st0= {num:6, spar:{&se[0][0], &se[3][0]}};
    state st1= {num:7, spar:{&so[0][0], &so[1][0]}};
    state st2= {num:8, spar:{&se[0][0], &se[1][0]}};

    int ni=3, min_index=0;// 初始状态解,最前状态解索引
    state st[3]= {st0, st1, st2};// 递推的初始解
    const int * st_tmp[2];// 临时存储获得解

    int i, j, k, m, n, p, find=0, count=25;
    for (i=9; i<=count; i++)
    {
        if ( (i%2)==0 )
            sp=&se[0][0];
        else
            sp=&so[0][0];

        for (p=0, m=0; p<2; p++, sp=sp+2)
        {
            find=0;
            for (k=0; k<3; k++)
            {
                for (n=0; n<2; n++)
                {
                    if ( ( (*(sp+1) ^ 2)==*(st[k].spar[n]+1) )
                            && ( (*sp & 2) != (*(st[k].spar[n]) & 2) )
                            && ( ((*(st[k].spar[n])+*sp+(i-st[k].num))%2)==0 ) )
                    {
                        st_tmp[m]=sp;
                        find=1;
                        m++;
                    }
                }
            }
            if (find==0)
            {
                st_tmp[m]=(sp+4);
                m=m+1;
            }
        }

        printf("state: %d   {%d %d}, {%d %d}\n", i, st_tmp[0][0], st_tmp[0][1], st_tmp[1][0], st_tmp[1][1]);
        st[min_index%3].num=i;
        st[min_index%3].spar[0]=st_tmp[0];
        st[min_index%3].spar[1]=st_tmp[1];
        min_index++;
    }
    return 0;
}

------解决方案--------------------
20L的输出结果错了,因为初始状态值st0手算错了,输出改为甲当前该抓的个数:

C/C++ code

#include <stdio.h>
#include <stdlib.h>

typedef struct {  // 状态描述三元组
    int num;
    const int * spar[2];
} state;

int main(void)
{
    const int se[4][2] = { {3,0}, {2,1}, {0,3}, {1,2} }; // 偶数石子时状态集,前2个均为甲的优先状态
    const int so[4][2] = { {2,0}, {3,1}, {0,2}, {1,3} }; // 奇数石子时状态集
    const int *sp = NULL; // 当前可能解

    state st0 = { 1, {&so[2][0], &so[1][0]} }; // 初始状态解 1、2、3
    state st1 = { 2, {&se[0][0], &se[1][0]} };
    state st2 = { 3, {&so[0][0], &so[1][0]} };
    int min_index = 0; // 最前状态解索引

    state st[3] = {st0, st1, st2}; // 初始状态解集,递推的初始解
    const int * st_tmp[2]= {0}; // 临时存储获得解(两个状态)

    int i, j, k, m, n, find=0, fetch[2]={0}, count=25;
    for (i=4; i<=count; i++) // 从4开始,直到25
    {
        if ( (i%2)==0 ) // 得到奇/偶状态集合
            sp=&se[0][0];
        else
            sp=&so[0][0];

        for (j=0, m=0; j<2; j++, sp=sp+2) // 验证可能解,2个优先状态
        {
            find=0;
            for (k=0; k<3; k++) // 3 个初始状态解集合
            {
                for (n=0; n<2; n++) // 每个初始解包含2个状态解
                {
                    if ( ( find == 0 )  // 禁止同一可能解匹配两次,导致数组越界
                        && ( (*(sp+1) ^ 2) == *(st[k].spar[n]+1) )  // 回溯规则,匹配可能的优先解
                        && ( (*sp & 2) != (*(st[k].spar[n]) & 2) )
                        && ( ((*(st[k].spar[n])+*sp+(i-st[k].num))&1) == 0 ) )
                    {
                        st_tmp[m] = sp; // 记录通过验证的解
                        fetch[m] = i-st[k].num; // 优先状态解的取子数
                        find = 1;
                        m++; // st_tmp 准备记录第二个状态解
                    }
                }
            }
            if ( find==0 ) // 若优先解未通过验证,则取镜像解
            {
                st_tmp[m] = (sp+4);
                fetch[m] = 0;
                m++;
            }
        }

        if ((fetch[0] && fetch[1]) | (i==count))
            printf("state: %d   %d(%d), %d(%d)\n", i, fetch[0], (st_tmp[0][0]&1), fetch[1], (st_tmp[1][0]&1));
        else
            printf("state: %d   %d\n", i, fetch[0]+fetch[1]);

        st[min_index].num = i; // 更新初始状态解
        st[min_index].spar[0] = st_tmp[0];
        st[min_index].spar[1] = st_tmp[1];
        if (++min_index  > 2)   min_index = 0;

    }

    return 0;
}
友情提示:
信息收集于互联网,如果您发现错误或造成侵权,请及时通知本站更正或删除,具体联系方式见页面底部联系我们,谢谢。

其他相似内容:

热门推荐: