【编程之美】2.20程序理解和时间分析

转自:http://blog.csdn.net/zhuhuiby/article/details/6742980

题目如下:
阅读以下C#代码,回答问题:

using System;
using System.Collections.Generic;
using System.Text;

namespace FindTheNumber
{
     class Program
     {
          static void Main(string[] args)
          {
               int [] rg =
               {2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,
               18,19,20,21,22,23,24,25,26,27,28,29,30,31};

               for(Int64 i = 1; i < Int64.MaxValue; i++)
               {
                    int hit = 0;
                    int hit1 = -1;
                    int hit2 = -1;
                    for (int j = 0; (j < rg.Length) && (hit <= 2); j++)
                    {
                          if((i % rg[j]) != 0)
                          {
                               hit++;
                               if(hit == 1)
                               {
                                    hit1 = j;
                               }
                               else if (hit == 2)
                               {
                                    hit2 = j;
                               }
                               else
                                    break; 

                          }
                    }

                    if((hit == 2) && (hit1 + 1 == hit2))
                    {
                          Console.WriteLine("found {0}", i);
                    }

               }
          }
     }
}

1> 这个程序要找的是符合什么条件的数?
2> 这样的数存在么?符合这一条件的最小的数是什么?
3>
在电脑上运行这一程序,你估计要多长时间才能输出第一个结果?时间精确到分钟(电脑配置:单核CPU2.0GHz,内存和硬盘资源充足)

----------------------------------------------------

我自己做的时候太想当然了,觉得是求 2-29的最小公倍数 没有考虑到 30可以分解为2*3*5

---------------------------------------------------------------------------

我的解答:

  • 1> 第一个问题不难,只要认真分析程序,就能看出来程序求的是这样的数,这个数不能被
    rg[k]和rg[k+1]整除(0 <= k <
    n-1),同时能被其余所有数(即rg[0],…,rg[k-1]和r[k+2],…,r[n-1])整除。
  • 2> 该问的入手点是寻找rg[k]和rg[k+1]。

首先,rg[k]肯定大于15。若rg[k]<=15的话,那么rg[k]*2也在rg数组中,并且不能被 i 整除,所以这样的 i 肯定找不到。

其次,rg[k]和rg[k+1]不能由其余的rg数组中的数组合相乘而得,比如18,可以由2乘上9得到,所以若 i 能整除 2 和 9, 则必能整除18.
由此,我们可以得到:

16=2*8, 18 = 2*9, 20 = 4*5, 22 = 2*11, 24 = 3*8, 26 = 2*13, 28 = 4*7, 30 =
2*15。

这样乍一看,似乎没有满足条件的rg[k]和rg[k+1],但是我们注意在上述一串等式中,16=2*8,其中的2是8的因子,所以只要 i
能整除8,就必能整除2,因此没有必要要求 i 能整除 2*8。 而其余的等式中,两个乘数没有因子关系,所以i 若能整除两个乘数,则肯定能整除其乘积。

由此,我们得到了唯一满足条件的rg[k]和rg[k+1],即16,17。

这样,剩下的问题就是求不能整除16,17,却能整除其余所有数的整数中最小的那一个。我们先把2到31中的素数都列出来(17除外):{2,3,5,7,11,13,19,23,29,31}。而2到31中(16,17除外)的数都是由这些素数作为因子组合相乘得到的,其中,要得到8,至少要3个2,要得到27至少要3个3,要得到25,至少要2个5,其余的素因子都只需一个就够了。

因此,这个最小的数就是 2^3, 3^3, 5^2, 7, 11, 13, 19, 23, 29,
31的乘积,答案为:2123581660200。(因为题目要求不借助电脑,所以我手算了两次,竟然都算错了,最后只好拿计算器算) 。

  • 3>
    要估算时间,我们先确定一个原子操作(或者说原子过程更合适),这里我们取内层for循环里的整个if语句块,该段程序主要包括一个取模操作和一个判断,如果进入if语句的话,还包括1次加法操作,1~2次判断和一次赋值操作。

我们知道加法、判断等操作基本都在几个时钟周期内就可以完成,而除法操作却需要数十个时钟周期,而取模操作也是通过除法操作得到的(还记得汇编语言里,执行除法操作之后,一个寄存器里存结果,另一个寄存器里存余数),另外,对64位整数的除法明显要慢于32位整数,综合这些因素,我们可以假设该原子操作需要100个时钟周期。因此2GHz的CPU在1秒内能跑2*10^9
/ 100 = 2*10^7 即2000万次原子操作,做过ACM的同学就会有一个直观概念,这和我们通常做时限为1S的题时估算的计算次数差不多。

接下来估算原子操作执行的次数:外层循环跑了2123581660200次,内层循环取决于 i
的情况,当i为奇数的时候,内层最多跑5次即可结束,因为2,4,6都不能整除奇数;当i为偶数的时候,情况要复杂一些,但是也可以一个一个的详细分析。这里我们粗略估计,就算内层循环平均可以跑10次,外层循环少跑一些,去掉零头,总的原子操作执行了2*10^13次。

所以需要 2*10^13 / (2*10^7) = 10^6秒约为277个小时。

JOJ的2042题目也是一个程序理解题目,这个题目非常有意思,给出了下面一段C++源代码,要求计算出最后的输出结果,源代码如下:

#include<cstdio>
int main(void)
{
     int x = 987654321, c = 0, d = 1, e = 6;
     while(x--){
         c += d,
         d += e,
         e += 6;
     }
     printf("%d/n", c);
     return 0;
}

原题目如下:

We can use axioms to calculate programs just like what we do in algebras.
Dijkstra is the one who advocates such constructive approach whereby a program
is designed together with its correctness proof. In short, one has to start from
a given postcondition Q and then look for a program that establishes Q from the
precondition. Often, analyzing Q provides interesting hints to finding the
program. This approach is quite different from the well known Hoare Logic.

For example, the following program is calculated by Dijkstra‘s approach.
Unfortunately, its annotation is lost so that its function is hard to grasp. You
are to help with finding the final value of the variable c. Note that the
program is designed under 128-bit architecture.

代码就是上面那一段。

这个题目通过小数据计算可以看出规律:x=1, c = 1; x=2, c=8; x=3, c=27; x=4,
c=64,于是可以猜测这段程序是用来计算x^3的。用计算器计算出987654321^3,提交上去就AC了。

这个题目是超级大牛SIYEE出的。从题目本身的叙述中就学到了很多东西。又知道了一个数的立方还可以这样计算。可惜数学功底差,不知道在数学上是如何推导出来的。

-----------------------------------------------------------------------------------------------

我自己把公式给推出来了:

        

初始条件:

      

容易得出:

      

进而得出:

      

最后得出:

      

时间: 2025-01-13 18:15:30

【编程之美】2.20程序理解和时间分析的相关文章

【编程之美】目录

第1章  游戏之乐——游戏中碰到的题目 1.1 让CPU占用率听你的指挥 1.2 中国象棋将帅问题 1.3 一摞烙饼的排序 1.4 买书问题 第2章  数字之魅——数字中的技巧 2.1 求二进制中1的个数 2.2 不要被阶乘吓倒 2.3 寻找发帖"水王" 2.4 1的数目 2.5 寻找最大的K个数 2.6 精确表达浮点数 2.7 最大公约数问题 2.8 找符合条件的整数 2.9 斐波那契(Fibonacci)数列 2.10 寻找数组中的最大值和最小值 2.11 寻找最近点对 2.12

编程之美需整理的题目汇总

2.1 求二进制中1的个数. 2.2 求n!末尾有多少个0 和 求n!的二进制表示中最低位1的位置. 2.3 找出数量超过总数一半的记录. 扩展:3个发帖都超过了总数N的1/4. 2.4 十进制1~N 所有整数中1的个数: 满足F(N)=N的最大的N是多少. 扩展:CC上所有2的个数. 2.5 寻找最大的k的个数. 2.6 精确表达浮点数 2.7 最大公约数问题. 2.8 任意给定一个正整数N,求一个最小的正整数M(M>1),似的N*M的十进制表示形式里只有1和0. 2.9 Fibonacci

【编程之美】java实现重建二叉树

package com.cn.binarytree.utils; /** * @author 刘利娟 [email protected] * @version 创建时间:2014年7月20日 下午2:03:30 类说明: */ class Node { Node left; Node right; char chValue; Node(char chValue) { left = null; right = null; this.chValue = chValue; } } public cla

编程之美问题之二叉树层序遍历多种解法

二叉树的层序遍历(要求区分层,例如每层遍历完输出换行) 单单层序遍历非常简单,一个队列就搞定了,但是区分层则要麻烦些.总的思路无非就是在每次print的时候,要能通过某个东西 区分出当前节点是否是一层最后一个节点,或者下一层的最后一个节点,感觉有点类似于机器学习中找个区分度明显的特征: 1.自己的解法,在单队列基础上,输入队列的数据添加一个标志 ,LevelHeaded,同时,在后面插入两个孩子的时候,判断是否这次输出的是队头,如果是的话,先插 个队头标志,再插入孩子,而且插入一次之后,后面不能

leetcode&amp;编程之美——博文目录

leetcode刷题整理: 1——Two Sum(哈希表hashtable,map) 2——Add Two Numbers(链表) 3——Longest Substring Without Repeating Characters(set,哈希表,两个指针) 9——Palindrome Number (数学问题) 11——Container With Most Water(两个指针) 12——Integer to Roman(string,数学问题) 13——Roman to Integer(s

编程之美之实时排名算法

首先来看一个实时排名算法 参考文献 某海量用户网站,用户拥有积分,积分可能会在使用过程中随时更新.现在要为该网站设计一种算法,在每次用户登录时显示其当前积分排名.用户最大规模为2亿:积分为非负整数,且小于100万. 存储结构 首先,我们用一张用户积分表user_score来保存用户的积分信息. 表结构: 示例数据: 下面的算法会基于这个基本的表结构来进行. 算法1:简单SQL查询 首先,我们很容易想到用一条简单的SQL语句查询出积分大于该用户积分的用户数量: select 1 + count(t

编程之美 找出符合条件的整数

好不容易把内容看懂~ 最主要的一句话:只需要将10k%N的结果与余数信息数组里非空的元素相加,再去模N,看看会不会出现新的余数~ 时间太紧迫~先把自己写的代码贴上,以后再详解 1 int FindMin(int N) 2 { 3 if(N <= 1) 4 return N; 5 6 int* A = new int[N];//这个是记录模N余i之后的数值 7 8 memset(A, -1, sizeof(int) * N); 9 int factor = 1; 10 A[1] = 1; 11 1

编程之美-1的个数

1的数目 题目:给定一个十进制正整数N,写下从1开始,到N的所有整数,然后数一下其中出现所有"1"的个数. 例如: N=2,写下1~2.这样只出现了1个"1". N=12,我们会写下1,2,3,4,5,6,7,8,9,10,11,12,这样,1的个数是5. 问题是: 1.写一个函数F(N),返回1到N之间出现的"1"的个数,比如F(12)=5; 2.满足条件"F(N)=N"的最大的N是多少?   我们就先来看看问题1的解法吧:

编程之美--3.8

题目描述:求二叉树节点的最大距离,距离是节点之间边的数目 思路:递归判断左子树右子树以及经过当前节点的值的大小 1 #include <iostream> 2 #include <queue> 3 #include <climits> 4 #include <algorithm> 5 #include <memory.h> 6 #include <stdio.h> 7 #include <ostream> 8 #inclu