在 飞鸟_Asuka网友指出“是不是时间复杂度比较大”,并说他“第一眼看到我就想把它当成一个数学问题来做”之后,我又重新对问题进行了数学式的思考后发现的。

这个BUG源于在(1<=A,B<=1000)条件下对矩形个数的数量级心里没数。当时觉得这个题目的目的是考察穷举,由于题目限定了A、B的范围,所以结果应该不是很大。事实证明这种想法是一厢情愿。

通常情况下,我不喜欢用数学方法解决C语言编程问题。因为很多问题,一旦在数学上能够轻易解决,编写代码往往是索然无趣的,对学习和练习C语言几乎没有什么益处。譬如,求1+2+3+……+100,如果不知道或假装不知道数学解法,代码可能是这样的

#include

int main( void)

{int i , sum = 0;for ( i = 1 ; i <=100 ; i ++)

{

sum+=i ;

}

printf("%d",sum);return 0;

}

而若使用数学方法,代码则是

#include

int main( void)

{

printf("%d", (1 + 100) * 100 / 2);return 0;

}

前者,C语言和编程成分的含量很高,数学含量却很低;而后者数学含量很高,可C语言和编程成分的含量几乎为0。

从解决实际问题的角度来说,显然应该用后一种方法;而从学习C语言和练习编程的角度来说,则应该使用前一种方法。所以一个好的编程问题,应该是没有数学解的,至少应该没有显而易见、容易得到的数学解。否则,只是用C语言对公式做一个简单的翻译(这大概是FORTRAN语言的哲学),编程的味道就全没有了,谭浩强的很多题目就是如此(参见:

所以,在飞鸟_Asuka 网友说他“第一眼看到我就想把它当成一个数学问题来做”之后,我也尝试着用数学的方式思考了一下。我发现,在同一条直线上的n个不同的点一共可以构成(n-1)n/2条不同的线段,A*B的矩形相邻两边各有A+1和B+1个不同的点,因而可以分别构成(A+1)A/2和(B+1)B/2条不同的线段,这样构成的矩形的个数一共就是(A+1)A/2×(B+1)B/2个。当A和B取最大值1000时,结果显然不小于25×1010,而这个值显然大于231-1,甚至也大于232-1(多数编译器中 unsigned 类型所能表示的最大整数)。这样,原来的重构代码中用int类型作为结果的类型,显然错了。

我一向认为C语言程序员应该心中有“数”,即对表达式中的数据和结果有最起码的范围估计。没想到,这次由于刻意回避数学解法,却立刻遭到了违背信条的报应。正应验了Muphry's law所言:Anything that can go wrong, will go wrong。

冯诺依曼也认为程序员至少应该清楚计算过程中数据的数量级,为此他反对浮点数。与之相反,约翰.巴科斯则盲目乐观地发明了浮点数,很多程序员尽情地享受浮点数的方便,却由于盲目乐观屡屡被浮点数这种“有缺陷的抽象”所伤。更有甚至,很多程序员连浮点数最基本的原理都不懂,竟然能写出k=sqrt(n) 这样狗屁不通的句子。(参见:

回过头来再谈我代码的BUG。这个BUG的另一个教训是没有进行比较充分的测试,如果测试一下边界情况可能不难发现这个BUG。后来我又重新测试了一下,发现程序运行时间比较长,这说明飞鸟_Asuka 网友指出“是不是时间复杂度比较大”的问题也是存在的。但当时为了算法叙述的方便,就没有按照下面的方法写count()函数:

int count( int A , intB )

{int x1 , y1 ;//第一个点的坐标

int x2 , y2 ;//第二个点的坐标

int num = 0;for ( x1 = 0 ; x1 < B ; x1 ++ )//for ( x1 = 0 ; x1 <= B ; x1 ++ )

for ( y1 = 0 ; y1 < A ; y1 ++ )//for ( y1 = 0 ; y1 <= A ; y1 ++ )//穷举第一个点的各种可能

for ( x2 = x1 + 1 ; x2 <= B ; x2 ++ )//for ( x2 = 0 ; x2 <= B ; x2 ++ )

for ( y2 = y1 + 1 ; y2 <= A ; y2 ++ )//for ( y2 = 0 ; y2 <= A ; y2 ++ )//穷举第二个点的各种可能

num ++ ; //{//if ( x1 < x2 && y1 < y2 )//num ++ ;//}

returnnum ;

}

如果写成这种形式,不难发现第二层循环与第三层是可以对调的,对调后为:

for ( x1 = 0 ; x1 < B ; x1 ++)for ( x2 = x1 + 1 ; x2 <= B ; x2 ++)for ( y1 = 0 ; y1 < A ; y1 ++)for ( y2 = y1 + 1 ; y2 <= A ; y2 ++)

num++ ;

这时应该能够看出,最内两层循环次数为 A + A-1 + A-2 +……+1,最外两层循环的循环次数为B + B-1 + B-2 +……+1,因而结果可以直接得到,即(A+1)A/2*(B+1)B/2。

算法问题解决了,又产生了新的问题,那就是如何表示这么大的整数,这是一个数据结构的问题。(或许,这才是题目的本意?)

办法之一就是使用表示范围更大的整数类型,例如C99中的long long int类型。

如果编译器不支持C99也没有表示范围更大的整数类型,那就只有自己着手构造新的数据结构了。

矩形个数的最大值大约为25×1010,这并不是一个很大的数,一个int不够,那就用两个好了。这里把存储大数的数据结构设计为一数组,

typedef int BIG_NUM[2] ;

数组的第0个元素存储低6位,第1个元素存储高位。存储低6位的原因是避免乘法运算时溢出(乘数不超过1001,与一个6十进制整数相乘不超过109,在int类型的表示范围之内)。

通过调用

BIG_NUM num ;

count( num , A , B );

将结果写到num中。

count()函数的实现:

void count( BIG_NUM m , int A , intB )

{

m[0] = 1;

m[1] = 0;

mul_sum( m , A );//将1+2+……+A的结果乘入m

mul_sum( m , B );//将1+2+……+B的结果乘入m

}

由于结果是累乘得到的,所以初始化为1。为防止溢出,只能

void mul_sum( BIG_NUM m , intn )

{int t1 = n % 2 == 0 ? n / 2: n ,

t2= n % 2 != 0 ? (n + 1) / 2 : n + 1;

mul( m , t1 );

mul( m , t2 );

}

小心翼翼地一个个地乘(每个乘数都不得超过1001)。t1,t2这两个变量是为了回避BIG_NUM类型的除法运算。

void mul( BIG_NUM m , intn )

{

m[0] *=n ;

m[1] *=n ;

m[1] += m[0]/1000000;//进位

m[0] %= 1000000;

}

每乘以一个数,立刻就处理进位问题。

最后还要考虑如何输出:

voidoutput( BIG_NUM m )

{if ( m[1] == 0)

{

printf("%d" , m[0] );return;

}

printf("%d" , m[1] );

printf("%06d" , m[0] );

}

这里的"%06"是为了保证低位不够6位时仍能正确输出。

完整的代码如下:

/*矩形的个数

在一个3*2的矩形中,可以找到6个1*1的矩形,4个2*1的矩形3个1*2的矩形,

2个2*2的矩形,2个3*1的矩形和1个3*2的矩形,总共18个矩形。

给出A,B,计算可以从中找到多少个矩形。

输入:

本题有多组输入数据(<10000),你必须处理到EOF为止

输入2个整数A,B(1<=A,B<=1000)

输出:

输出找到的矩形数。

样例:

输入:

1 2

3 2

输出:

3

18

作者:薛非

出处:http://www.cnblogs.com/pmer/“C语言初学者代码中的常见错误与瑕疵”系列博文*/#includetypedefint BIG_NUM[2] ;void count( BIG_NUM , int , int);void mul_sum( BIG_NUM , int);void mul( BIG_NUM , int);voidoutput( BIG_NUM );#define POW 1000000

#define WID 6

int main( void)

{intA , B ;while ( printf( "输入2个整数A,B(1<=A,B<=1000)"),

scanf("%d%d" , &A , &B )!=EOF

)

{

BIG_NUM num ;

count( num , A , B );

output( num );

}return 0;

}voidoutput( BIG_NUM m )

{if ( m[1] == 0)

{

printf("%d" , m[0] );return;

}

printf("%d" , m[1] );

printf("%0*d" , WID , m[0] );

}void mul( BIG_NUM m , intn )

{

m[0] *=n ;

m[1] *=n ;

m[1] += m[0]/POW;//进位

m[0] %=POW;

}void count( BIG_NUM m , int A , intB )

{

m[0] = 1;

m[1] = 0;

mul_sum( m , A );//将1+2+……+A的结果乘入m

mul_sum( m , B );//将1+2+……+B的结果乘入m

}void mul_sum( BIG_NUM m , intn )

{int t1 = n % 2 == 0 ? n / 2: n ,

t2= n % 2 != 0 ? (n + 1) / 2 : n + 1;

mul( m , t1 );

mul( m , t2 );

}

写的有点丑。

c语言一个数中是否含有8,要心中有“数”——C语言初学者代码中的常见错误与瑕疵(8)...相关推荐

  1. C语言初学者代码中的常见错误与瑕疵(9)

    题目 字母的个数 现在给你一个由小写字母组成字符串,要你找出字符串中出现次数最多的字母,如果出现次数最多字母有多个那么输出最小的那个. 输入:第一行输入一个正整数T(0<T<25) 随后T ...

  2. 为什么c语言加法错误,分数的加减法——C语言初学者代码中的常见错误与瑕疵(12)...

    重构 题目的修正 我抛弃了原题中"其中a, b, c, d是一个0-9的整数"这样的前提条件,因为这种限制毫无必要.只假设a, b, c, d是十进制整数形式的字符序列. 我也不清 ...

  3. c语言间接级别不同_一个超复杂的间接递归——C语言初学者代码中的常见错误与瑕疵(6)...

    问题: 在该文的最后,曾提到完成的代码还有进一步改进的余地.本文完成了这个改进.所以本文讨论的并不是初学者代码中的常见错误与瑕疵,而是对我自己代码的改进和优化.标题只是为了保持系列的连续性. 改进 程 ...

  4. C语言初学者代码中的常见错误与瑕疵(2)

    问题: 另一种阶乘 大家都知道阶乘这个概念,举个简单的例子:5!=1*2*3*4*5. 现在我们引入一种新的阶乘概念,将原来的每个数相乘变为i不大于n的所有奇数相乘 例如:5!!=1*3*5.现在明白 ...

  5. Android中打包含有Activity以及资源文件的jar包在工程中调用

    如何将资源以及activity文件打包成jar文件供项目调用,从android的官方文档中找到的完美的解决,这里做一个总结. 最近刚刚发布了一款小应用<诗词大全>,有兴趣的朋友请实用提出您 ...

  6. php 替换指定标签中的内容,php如何根据不同的条件替换html代码中的img标签

    这篇文章给大家介绍的内容是关于php根据不同的条件替换一段html代码中的不同的img标签,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 一.需求 这次的需求是获取到一段html代码 ...

  7. Hadoop中的MapReduce框架原理、数据清洗(ETL)、MapReduce开发总结、常见错误及解决方案

    文章目录 13.MapReduce框架原理 13.7 数据清洗(ETL) 13.7.1 需求 13.7.1.1 输入数据 13.7.1.2 期望输出数据 13.7.2 需求分析 13.7.3实现代码 ...

  8. c语言四个数找大wxyz,2015年计算机二级《C语言》考试上机测试题(7)

    31.有以下程序: #include main( ) {char a[20],b[20],c[20]; scanf("%s%s",a,b); gets(c); printf(&qu ...

  9. c语言中把各位上为奇数的数取出,下列给定程序中函数fun()的功能是:将长整型数中每一位上为奇数的数依次取出,构成一个新数放在冲。 - 赏学吧...

    下列给定程序中函数fun()的功能是:将长整型数中每一位上为奇数的数依次取出,构成一个新数放在冲.高位仍在高位,低位仍在低位.例如当s中的数为87653142时,t中的数为7531. 请改正函数fun ...

最新文章

  1. Ruby之父:写Ruby时工作特别闲,总加班的人很难做出创造
  2. js中bind、call、apply函数的用法
  3. Windows添加在此处打开命令CMD
  4. 系统地学学喝酒的技巧
  5. 专科程序员吐槽:学历是硬伤!问:想进大厂试试必须学历够格么?
  6. zzuli 2269:minval
  7. linux内核c1bcbc40,【资料共享】给学习linux内核的大餐
  8. 全民果园为什么服务器在维修中,你在果园管理中遇到的问题这里可以解决
  9. 译]bootstrap-select (selectpicker)方法
  10. php 5.6 闭包,PHP 闭包那点事儿
  11. 软件_git异常错误[博]
  12. portlet_Portlet生命周期
  13. 使用GeoServer发布Shapfile数据
  14. 计算机主机自动关机如何设置,WinXP电脑怎么设置自动关机?
  15. ZooKeeper篇:2PC、3PC以及ZAB协议
  16. springBoot Admin整合nacos
  17. SLAM发展现状研究
  18. c语言欺凌,《中国校园欺凌调查报告》发布 语言欺凌占主导
  19. Feature-Driven Robust Surgery Scheduling 搬运
  20. *srv.exe蠕虫病毒打开exe程序弹浏览器窗体的解决方案

热门文章

  1. sqlplus导数_GitHub - Erik-Yim/blog: Everything about database,bussiness.(Most for PostgreSQL).
  2. 科研,办公几款强大又实用的软件(含安装包)
  3. 【最优化算法】基于【MATLAB】的拟牛顿法【Quasi Newton method】分析与推导
  4. C++ 实现斗地主玩法
  5. 惠普z系列服务器,惠普Z8/Z6/Z4 G4新款工作站/服务器主机:Quadro P6000 48TB存储
  6. 计算机基础——计算机网络
  7. Xfire+WebService “Discarding unexpected response: HTTP/1.1 100 Continue”错误解决
  8. 科研工具篇|看完之后能提高你80%的科研工作效率
  9. 显卡1060和1660测试对比
  10. gamemaker学习笔记:拖拽