杂记:记三年级表弟的一道数学题

      前几天我妈给我发来一道题,说是我表弟(他读三年级)的一道数学题,问我会不会:

表弟的数学题
      我大概看了一下,有几个格子很好填出来,比如最中间的4,还有几个格子是可以知道里面的数的组合(具体的排列情况可能要根据每行每列是否冲突来进行调整),至于在这之后, 我想到的办法就是一个一个地去挑格子去试,一旦不对就回到前几步,除此之外我没想到什么更好的办法。

      既然都用这种笨办法一个个去试了,何不用计算机去试呢2333,毕竟干傻事人脑可干不过电脑呀。回顾我刚才手算的策略,这不就是典型的试探——回溯的算法嘛。只不过我自己做的时候,倾向于在整张表格里先挑明显看得出来是由哪几个数组成的格子,然后尝试不同的排列方式,一个个去试过去,像是那种“先在一个根据地扎根,然后一步步拓宽解放区”的策略。至于计算机来的话,就不需要这么复杂了,这题的数据量不大,直接枚举所有可能的7*7数独组合,然后用原题里“规则”下面的约束剪枝就可以(不剪枝会死得很难看,枚举全排列的话是 O(7^49) ,枚举数独排列(每行每列不重不漏)的话,也是阶乘级别的复杂度的)。按照这个思路,这题就是道经典的dfs暴力搜索题了(不过就我自己而言是还没学到图论和图算法这一块的,只是之前做过一道应用dfs的题目,所以大概有点点概念的那种)。我采用一维数组,按从左到右、从上到下的顺序来存储这个表格。

      话不多说,代码伺候:

#include <cstdio>
#include <cstring>

int a[49] = {0};
inline int sum(int a, int b) { return a + b; }
inline int sum(int a, int b, int c) { return a + b + c; }
inline int dif(int a, int b) { return a > b ? (a - b) : (b - a); }

inline bool isLegal(int *a, int index)
{	//这里判断的条件就是对应原题“规则”下面的第3,第4,第5条的要求
    bool f[49];
    memset(f, true, sizeof(f));
    //l1
    f[2] = (sum(a[0], a[1], a[2]) == 10);
    f[4] = (sum(a[3], a[4]) == 13) || (dif(a[3], a[4]) == 13);
    f[13] = (sum(a[5], a[6], a[13]) == 8);
    f[14] = (sum(a[7], a[14]) == 5) || (dif(a[7], a[14]) == 5);
    //l2-l3
    f[9] = (sum(a[8], a[9]) == 3) || (dif(a[8], a[9]) == 13);
    f[17] = (sum(a[10], a[17]) == 7) || (dif(a[10], a[17]) == 7);
    f[12] = (sum(a[11], a[12]) == 13) || (dif(a[11], a[12]) == 13);
    f[16] = (sum(a[15], a[16]) == 11) || (dif(a[15], a[16]) == 11);
    f[25] = (sum(a[18], a[25]) == 2) || (dif(a[18], a[25]) == 2);
    f[20] = (sum(a[19], a[20]) == 11) || (dif(a[19], a[20]) == 11);
    //l4-l7
    f[35] = (sum(a[21], a[28], a[35]) == 12);
    f[29] = (sum(a[22], a[29]) == 11) || (dif(a[22], a[29]) == 11);
    f[31] = (sum(a[23], a[30], a[31]) == 7);
    f[24] = (a[24] == 4);
    f[33] = (sum(a[26], a[33]) == 9) || (dif(a[26], a[33]) == 9);
    f[34] = (sum(a[27], a[34]) == 4) || (dif(a[27], a[34]) == 4);

    f[37] = (sum(a[36], a[37]) == 13) || (dif(a[36], a[37]) == 13);
    f[45] = (sum(a[38], a[44], a[45]) == 14);
    f[46] = (sum(a[32], a[39], a[46]) == 7);
    f[41] = (sum(a[40], a[41]) == 4) || (dif(a[40], a[41]) == 4);
    f[48] = (sum(a[47], a[48]) == 7) || (dif(a[47], a[48]) == 7);
    f[43] = (sum(a[42], a[43]) == 9) || (dif(a[42], a[43]) == 9);
	//下面的for循环用来给dfs剪枝,一旦发现当前已有的组合不满足条件,立马返回false
    for (int i = 0; i < index; ++i)
    {
        if (f[i] == false)  return false;
    }
    return true;
}

inline bool isInterupt(int *a, int index, int cp)
{	//这个函数也是拿来剪枝的。它用来判断生成当前的组合每行每列是否有重复的元素(这个条件等价于“规则”里的第2条)
    int current_line = index / 7 * 7;
    int current_column = index % 7;
    for (int i = current_line; i < current_line + 7; ++i)
    {
        if (i != index && cp == a[i])
            return true;
    }
    for (int j = current_column; j < 49; j += 7)
    {
        if (j != index && cp == a[j])
            return true;
    }
    return false;
}

inline void display(int *a)
{	//打印最后的答案
    for (int i = 0; i < 49; ++i)
    {
        if (a[i] == 0)
            return;
    }
    for (int i = 0; i < 49; i++)
    {
        printf("%d ", a[i]);
        if (i % 7 == 6)
            printf("\n");
    }
}

void dfs(int *a, int cur)
{
    if (!isLegal(a, cur))    return;
    if (cur == 49)
    {
        display(a);
        return;
    }
    //这一步是一个试探——回溯的过程,先对当前位从1-7试探,如果不冲突,则设置数位以后转到下一位;
    for (int i = 1; i <= 7; i++)
    {
        if (!isInterupt(a, cur, i))	//按照当前排列是否满足
        {	
            a[cur] = i;
            dfs(a, cur + 1);
        }
        a[cur] = 0;	//如果冲突(意味着1-7全都试过一遍了),则把当前一位置0然后return回上一个数位继续试探(否则影响上一个数位的isInterupt判定)
    }
}

int main()
{
    printf("start...\n");
    dfs(a, 0);
    return 0;
}

      最后此题答案是:

5 2 3 6 7 1 4 
4 1 2 5 6 7 3 
1 5 6 2 3 4 7 
3 7 1 4 5 6 2 
7 4 5 1 2 3 6 
2 6 7 3 4 5 1 
6 3 4 7 1 2 5 

      这个算法写的时候,注意回溯前把当前位置0,然后剪枝剪好就行。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值