【算法编程】小学数学题难倒博士

昨天在科学网上得知这样一个新闻《越南小学数学题难倒博士》,据悉题目来自越南保禄小学三年班,不过报道称该题难倒了上至博士下至家长,未免也太言过其实了。


题目描述

学生需要在下图表格中按由上至下、从左到右的顺序,填入1~9的数字,可重复填写,并按先乘除后加减(图中冒号代表除法)的运算法则,完成整条算式。


解题方法

显然,这题对于我们这种程序员来说完全不是问题,只要在大一上过C语言的学生(我们学校全校都学过C,即使是文科专业)基本上都可以用九重for循环来穷举解出此题,下面我分别用C和Matlab实现,并对Matlab算法进行了改进。


C语言实现:

#include<stdio.h>
#include<time.h>
void main()
{
    clock_t start, finish; //用于计时
    double   duration;
    start = clock();
    double result=0;//存储计算结果来看是否与66相等
    int index=0;
    int num=0;
    for(int a=1;a<10;a++)
        for(int b=1;b<10;b++)
            for(int c=1;c<10;c++)
                for(int d=1;d<10;d++)
                    for(int e=1;e<10;e++)
                        for(int f=1;f<10;f++)
                            for(int g=1;g<10;g++)
                                for(int h=1;h<10;h++)
                                    for(int i=1;i<10;i++)
                                    {
                                        result=a+13*b/float(c)+d+12*e-f-11+g*h/float(i)-10;
                                        if(result==66)
                                        {
                                            //这里可以打印解的结果
                                            num=num+1;
                                        }
                                    }
    finish = clock();
    duration = (double)(finish - start) / CLOCKS_PER_SEC;
    printf("总共有%d种结果\n耗时为%f秒\n",num,duration);
}

结果显示如下:


Matlab实现

  • 最直接的方法:耗时4911.131591 秒。
clear all;ans=[];
tic
for a=1:9

   for b=1:9

      for c=1:9

          for d=1:9

               for e=1:9

                   for f=1:9

                       for g=1:9

                           for h=1:9

                               for i=1:9

                                   result=a+13*b/c+d+12*e-f-11+g*h/i-10;

                                   if result==66;

                                      answer=[a b c d e f g h i]; ans=[ans;answer];                              

                                   end

                               end

                           end

                       end

                   end

               end

          end

      end

   end

end
toc

在上面的算法中,存储结果answer的操作十分耗时,我们可以想办法来优化。于是,我将存储结果的过程注释掉后(去掉if语句块),耗时为16.814298 秒,时间由一个小时缩短到了十几秒。不过即使不储存结果,也耗时16.814298 秒,这与用C语言(同样也没有存储结果)的4秒还是有差距的!下面我来讲讲如何改进算法。


改进的matlab实现

在之前的文章《Matlab高效编程技巧》中,提到了要尽量避免多重循环,多使用向量化函数。因此,我决定用矩阵来代替这9重循环。

首先从简单的例子出发:假设有2个一维数组a,b,其元素都是1:9,显然这2个数组任意元素之间进行四则运算(在这里我们假设是相乘)的结果有9*9项,可以用一个9*9的二维数组表示;同理,假设有3个一维数组a,b,c,其元素都是1:9,我们要计算这三个数组任意元素之间进行四则运算的结果,这样总共有9*9*9项,正好用一个9*9*9的三维数组存储;依此类推,我们可以得到9个一维数组元素间进行四则运算可以用9*9*9*9*9*9*9*9*9*9的9维数组表示。

然而,在matlab中,*只能用于二维数组的相乘,幸好我们可以通过bsxfun函数来进行不同维数数组的计算.下面举例演示一下bsxfun的用法:

clear all

a=ones(9,1);%注意一维列向量相当于一个大小为9*1的二维向量
b=ones(1,9);
c=ones(1,1,9);

a(1:9)=1:9
b(1,1:9)=1:9
c(1,1,1:9)=1:9;

temp1=bsxfun(@times,a,b)%乘法a*b temp1是9*9的二维数组

temp2=bsxfun(@plus,a,b)%加法a+b

temp3=bsxfun(@times,temp1,c)%乘法a*b*c 9*9*9的三维数组

通过运行上述结果,你就可以发现,bsxfun完成了任意元素间两两进行四则运算的结果,而且并不要求维数相等。当然关于bsxfun的运算原理以及作用可以查看Matlab的自带文档。我们的算法只需要上述的功能就可以了,在程序中,我按照公式a+13*b/c+d+12*e-f-11+g*h/i-10计算了当a,b,?,i的所有组合的值,并存储在abcdefghi中,最后再找到数组abcdefghi中值为66的元素所在的下标索引,其索引就是问题的解。具体的Matlab程序实现如下:

clear all
tic

%使得a,b,c,d,e,f,g,h,i分别为1,2,3,4,5,6,7,8,9维的向量
a=ones(9,1);%注意一维列向量相当于一个大小为9*1的二维向量
b=ones(1,9);
c=ones(1,1,9);
d=ones(1,1,1,9);
e=ones(1,1,1,1,9);
f=ones(1,1,1,1,1,9);
g=ones(1,1,1,1,1,1,9);
h=ones(1,1,1,1,1,1,1,9);
i=ones(1,1,1,1,1,1,1,1,9);

a(1:9)=1:9;
b(1,1:9)=1:9;
c(1,1,1:9)=1:9;
d(1,1,1,1:9)=1:9;
e(1,1,1,1,1:9)=1:9;
f(1,1,1,1,1,1:9)=1:9;
g(1,1,1,1,1,1,1:9)=1:9;
h(1,1,1,1,1,1,1,1:9)=1:9;
i(1,1,1,1,1,1,1,1,1:9)=1:9;

%主要使用bsxfun函数来实现不同维函数的四则运算
%a+13*b/c+d+12*e-f-11+g*h/i-10=66
b=bsxfun(@times,b,13);
bc=bsxfun(@rdivide,b,c);
gh=bsxfun(@times,g,h);
ghi=bsxfun(@rdivide,gh,i);

abc=bsxfun(@plus,a,bc);
abcd=bsxfun(@plus,abc,d);
e=bsxfun(@times,e,12);
abcde=bsxfun(@plus,abcd,e);
abcdef=bsxfun(@minus,abcde,f);
abcdef=bsxfun(@minus,abcdef,11);
abcdefghi=bsxfun(@plus,abcdef,ghi);
abcdefghi=bsxfun(@minus,abcdefghi,10);
toc
counter=find(abcdefghi==66);%找到下标索引
[l1,l2,l3,l4,l5,l6,l7,l8,l9]=ind2sub(size(abcdefghi),counter(1))%这就是一种可能的解

运行结果如下图:

从图中可以看到,此方法耗时4.183608秒,图中我只显示了前十个结果,并且只计算了第一个结果45所对应的abcdefghi的值,其值在下图的变量空间中:

从上图中可以看出,counter大小为442232,即总共有442232个解,其中counter(1)=45时,对应的解为abcdefghi=9 5 1 1 1 1 1 1 1 1 1。

注释:此程序在内存较小的电脑中会由于内存不够而运行不成功,我是在实验室的工作站(内存128g)上运行的。


结果分析

使用matlab编程时,要避免使用多重循环,尽量以矩阵的角度思考问题。由上面的程序耗时对比可以看出,用C语言实现和我改进的算法耗时都在4秒左右,而且用C语言实现是在没有存储解的结果的情况下,如果同样的要存储结果(存储结果可以用不同的数据结构:链表、队列等等)的话,谁更耗时还说不定!由文中提到的两种matlab实现可知,第一种方法占用内存小,可以在普通的电脑上运行,但是耗时长;而我们改进的算法,耗时短,但是占用内存大,在内存小的机器上无法运行。这就是所谓的时间换空间,空间换时间吧!



原文:http://blog.csdn.net/tengweitw/article/details/45014771

作者:nineheadedbird

时间: 2024-08-02 02:39:18

【算法编程】小学数学题难倒博士的相关文章

我也来试试“越南小学三年级数学题难倒博士”的难题

今天的百度排行榜,一个标题为”越南数学题难倒博士“的新闻热点吸引了我,于是猛戳 原文链接为:http://news.sina.com.cn/w/p/2015-05-21/100531859030.shtml: 题目大致是这样的: 学生需要由上至下.从左到右的顺序,填入1至9的数字,可重复填写,并按先乘除后加减的运算法则,完成整条算式. 河内一家人才培育中心的副总监陈方表示,题目只需用到基本运算技巧,他邀请一些成年人挑战,包括拥有经济学博士学位的人,不过全部人都答不出来. 在越南教育科学研究所修读

越南一难倒博士的趣味数学题

越南有一道难倒博士的趣味数学题,见下图: 在空格中填入1...9,可以重复,求使等式成立的一个组合 ======================================= 我吐槽一下,这题在NOIP中肯定算水题了,爆搜都能过.O(9n),n=9 我就不具体代码实现了. ======================================= 据说有人跟我一样的想法 河内一家人才培育中心的副总监陈方表示,题目只需用到基本运算技巧,他邀请一些成年人挑战,包括拥有经济学博士学位的人,不过

【算法编程】过河问题

今天偶尔想到了过河问题.记得读小学六年级的时候第一次接触到这个问题--六个老虎过河问题(百度上有详细介绍,本文解决的是一个简单的问题,下一篇文章中将讨论该问题),当时都是从逻辑思维的方法得到正确的解决方法.本文介绍了普遍适用该类问题的方法以及该方法的改进方法,下一篇文章将介绍问题的变型及解法. 向量法(人.狗.鸡.米过河问题) 问题描述:某人带狗.鸡.米用船来过河,只有人会划船(好像是废话,后面问题我们还会假设动物也会划船),另外至多还能载一物,当人不在时,狗要吃鸡(有人可能会质疑:狗吃鸡?,但

一维向量旋转算法 编程珠玑 第二章

看了编程珠玑第二章,这里面讲了三道题目,这里说一下第二题,一维向量旋转算法. 题目:将一个n元一维向量(例数组)向左旋转i个位置. 解决方法:书上讲解了5种方法,自己只想起来2种最简单方法(下面讲的前两种). 1.原始方法. 从左向右依次移动一位,对所有数据平移:这样循环i次,算法最坏时间复杂度达n^2.耗时不推荐. 2.空间换时间. 顾名思义,申请一个i长度的空间,把前i半部分放到申请空间中,再把后面的所有数据向左移动i个位置,最后把申请的空间中的数据放到后半部分.浪费空间,不推荐. 3.杂技

LeetCode算法编程(两题)

今天看到酷壳推荐的国外编程LeetCode算法编程网站,上面目前有154道算法题,感觉很有意思,平常工作也比较忙,现在很少有时间来锻炼算法相关的东西,有空的时候静下心来,温习下基础,活跃下自已的思路,也是有必要的.先做了几道,后面会陆续补充其它的题目. 1.题目-PlusOne Given a non-negative number represented as an array of digits, plus one to the number. The digits are stored s

【算法编程】循环右移一个数组

仅用一个辅助节点将一个大小为n数组循环右移k位的三种办法: 1.时间复杂度最大:将所有元素每次只移动一位,总共移动k次,程序实现十分容易,在此就不具体实现了. 2.时间复杂度适中:依次将每个元素都放到辅助节点上,然后将其储存到目的节点,具体程序如下: #include<iostream> using namespace std; int gcd(int x,int y); int main() { int n,k; cout<<"请输入数组的维数"<<

阶乘求和与冒泡算法编程

编程题是写1!+2!+...+10!: 冒泡算法编程

小学数学题

小学数学题1: "UseTyp"="1" 012345678901 目标:截取字符串UseTyp(注意,UseTyp的长度是可变的) 问题:求以上字符串UseTyp的长度(注意UseTyp的长度是不固定) 设UseTyp的长度 = p 已知1: =号的索引位置x(IndexOf("=")) 已知理论2: 字符串长度 = 最大索引 + 1 所以, p = x + 1 – 3 p = x - 2 所以,字符串的截取方法为 Substring(1 ,

小学数学题大礼包

小学数学题大礼包 一.预估与实际 PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟) Planning 计划 30 60 ? Estimate ? 估计这个任务需要多少时间 600 650 Development 开发 600 730 ? Analysis ? 需求分析 (包括学习新技术) 60 100 ? Design Spec ? 生成设计文档 50 8 ? Design Review ? 设计复审 5 20 ? Coding