首先我想吐槽的是,在CSDN上搞了好久还是不能发博客,就是点下发表丝毫反应都没有的,我稍微百度了几次还是没有找到解决方法,在CSDN的BBS上也求助过管理员但是没有收到答复真是烦躁,导致我新生入学以来没能很好的在博客上记录点什么,有些想法只是在脑中灵光一现然后事后就不好想起来了。后来发现了除了CSDN还有ITeye、cnblogs等一些其他优秀的博客网站…。废话不多说了,现在开始吧。

背景:在大一上学期的第六次C语言课后练习中碰到了这样一题

放苹果(POJ1664

题目描述

把M个同样的苹果放在N个同样的盘子里,允许有盘子空,问共有多少种不同分法
注意:5,1,1和1,5,1是同一种。

输入

第一行是测试数据数目t(0~20),以下t行均包含两个整数M,N,空格分开。
1<=M,N<=10;

输出

对每行数据,输出相应的k。

样例输入

1

7 3

样例输出

8

审题思考:

在经过小范围数据的纸上运行后,发现了这么一个规律:

0 0 0

0 0 7 OK

0 1 1

0 1 6 OK

0 2 2

0 2 5 OK

0 3 3

0 3 4 OK

0 4 4

0 5 5

0 6 6

0 7 7

1 1 1

1 1 5 OK

……

于是,按照这个规律,我试着这么写:

当M=7,N=3的时候:

 1 if(N==3)
2 {
3 for(i=0;i<M;i++)
4 {
5 for(j=i;j<M;j++)
6 {
7 for(k=j;k<=M;k++)
8 {
9 if((i+j+k)==M)
10 {
11 ti++;
12 break;
13 }
14 }
15 }
16 }
17 printf("%d\n",ti);
18 }

同理,N在1-10这10个数中,都可以利用此种多重FOR循环嵌套进行搜索判断

按照这种规律,我完整的写了一遍这题,但是发现提交上去WA

以下是我完整的代码:

   #include<stdio.h>
int main()
{
int n,i,j,k,M,N,ti,i4,i5,i6,i7,i8,i9,i10;
while(scanf("%d",&n)!=EOF)
{
while(n--)
{
ti=;
scanf("%d%d",&M,&N);
if(N==)
printf("1\n");
if(N==)
{
for(j=;j<M;j++)
{
for(k=j;k<=M;k++)
{
if((j+k)==M)
{
ti++;
break;
}
}
}
printf("%d\n",ti);
}
if(N==)
{
for(i=;i<M;i++)
{
for(j=i;j<M;j++)
{
for(k=j;k<=M;k++)
{
if((i+j+k)==M)
{
ti++;
break;
}
}
}
}
printf("%d\n",ti);
}
if(N==)
{
for(i4=;i4<M;i4++)
{
for(i=i4;i<M;i++)
{
for(j=i;j<M;j++)
{
for(k=j;k<=M;k++)
{
if((i4+i+j+k)==M)
{
ti++;
break;
}
}
}
}
}
printf("%d\n",ti);
}
if(N==)
{
for(i5=;i5<M;i5++)
{
for(i4=i5;i4<M;i4++)
{
for(i=i4;i<M;i++)
{
for(j=i;j<M;j++)
{
for(k=j;k<=M;k++)
{
if((i5+i4+i+j+k)==M)
{
ti++;
break;
}
}
}
}
}
}
printf("%d\n",ti);
}
if(N==)
{
for(i6=;i6<M;i6++)
{
for(i5=i6;i5<M;i5++)
{
for(i4=i5;i4<M;i4++)
{
for(i=i4;i<M;i++)
{
for(j=i;j<M;j++)
{
for(k=j;k<=M;k++)
{
if((i6+i5+i4+i+j+k)==M)
{
ti++;
break;
}
}
}
}
}
}
}
printf("%d\n",ti);
}
if(N==)
{
for(i7=;i7<M;i7++)
{
for(i6=;i6<M;i6++)
{
for(i5=i6;i5<M;i5++)
{
for(i4=i5;i4<M;i4++)
{
for(i=i4;i<M;i++)
{
for(j=i;j<M;j++)
{
for(k=j;k<=M;k++)
{
if((i7+i6+i5+i4+i+j+k)==M)
{
ti++;
break;
}
}
}
}
}
}
}
}
printf("%d\n",ti);
}
if(N==)
{
for(i8=;i8<M;i8++)
{
for(i7=;i7<M;i7++)
{
for(i6=;i6<M;i6++)
{
for(i5=i6;i5<M;i5++)
{
for(i4=i5;i4<M;i4++)
{
for(i=i4;i<M;i++)
{
for(j=i;j<M;j++)
{
for(k=j;k<=M;k++)
{
if((i8+i7+i6+i5+i4+i+j+k)==M)
{
ti++;
break;
}
}
}
}
}
}
}
}
}
printf("%d\n",ti);
}
if(N==)
{
for(i9=;i9<M;i9++)
{
for(i8=;i8<M;i8++)
{
for(i7=;i7<M;i7++)
{
for(i6=;i6<M;i6++)
{
for(i5=i6;i5<M;i5++)
{
for(i4=i5;i4<M;i4++)
{
for(i=i4;i<M;i++)
{
for(j=i;j<M;j++)
{
for(k=j;k<=M;k++)
{
if((i9+i8+i7+i6+i5+i4+i+j+k)==M)
{
ti++;
break;
}
}
}
}
}
}
}
}
}
}
printf("%d\n",ti);
}
if(N==)
{
for(i10=;i10<M;i10++)
{
for(i9=;i9<M;i9++)
{
for(i8=;i8<M;i8++)
{
for(i7=;i7<M;i7++)
{
for(i6=;i6<M;i6++)
{
for(i5=i6;i5<M;i5++)
{
for(i4=i5;i4<M;i4++)
{
for(i=i4;i<M;i++)
{
for(j=i;j<M;j++)
{
for(k=j;k<=M;k++)
{
if((i10+i9+i8+i7+i6+i5+i4+i+j+k)==M)
{
ti++;
break;
}
}
}
}
}
}
}
}
}
}
}
printf("%d\n",ti);
}
}
}
return ;
}

但是对于用这种方法解决这题我并不是很满意,虽然不知道为什么WA了。

在查阅了相关资料后发现可以利用DFS的方式

这里有一段关于解决这题的DFS相关函数:

    //代码来西电ACMPPT
void dfs(long k, long w) //初始k=1,w=0; k表示现在已用几个盘子,w表示已经放了几个苹果
{
long i,u;
if (k==m) //当最后一个盘子被放置苹果的时候我们进行判断
{
if (n-w>=s[k-])
{
s[k]=n-w;
z=z+;
}
return;
}
for (i=;i<=n;i++)
if (i>=s[k-]) //如果当前放置的苹果个数大于前一个盘子继续放置
{
w=w+i;
s[k]=i; k=k+;
dfs(k,w);
w=w-i;
k=k-;
}
}

当初看到这段简短的代码,我只是一眼看出了这是与递归相关的函数

花了一段时间纸上运行后(在纸上写出详细步骤)

大致写出我的纸上运行内容:

以实例输入为5个苹果,3个盘子

Start: k=1,w=0

第一次进入DFS函数

W=0,s[1]=0,k=2;

第二次进入DFS函数

W=0,s[2]=0,k=3;

第三次进入DFS函数

S[3]=5,z=z+1;

退出第三次进入的DFS函数,返回第二次进入的DFS函数

I=1,w=1,s[2]=1,k=3

第三次进入DFS函数

S[3]=4,z=z+1;

退出第三次进入的DFS函数,返回第二次进入的DFS函数

W=0,k=2;

I=2,w=1,s[2]=2,k=3;

第三次进入DFS函数

S[3]=3,z=z+1

退出第三次进入的DFS函数,返回第二次进入的DFS函数

!暂停

在此,我们已经得到三组答案数据,即 0 0 5、 0 1 4和 0 2 3

并且得到Z=3;

那么按照这个规律,我们可以得出一共会有七组答案数据:

0 0 7

0 1 6

0 2 5

0 3 4

1 1 5

1 2 4

1 3 3

2 2 3

在此,我们已经可以利用DFS的方法解决这道题目,当然

这样解决的效率并不是很高,所有需要剪枝

优化的代码为:

    //代码来自西电ACMPPT
void dfs(long k, long w)
{
long i,u;
if (k==m)
{
if (n-w>=s[k-])
{
s[k]=n-w;
z=z+;
}
return;
}
for (i=;i<=n;i++)
if ((i>=s[k-])&&((n-w)/(m-k)>=s[k-])) //优化剪枝部分
{
w=w+i;
s[k]=i;
k=k+;
dfs(k,w);
w=w-i;
k=k-;
}
}

到此为止,本题结束,但是我还想再提一题:

问题 C: 伯努利装错信封问题-综合[难]

时间限制: 30 Sec  内存限制: 128 MB
提交: 417  解决: 199
[提交][状态][讨论版]

题目描述

某人写了n封信,同时为每一封信写1个信封,共n个信封。如果把所有的信都装错了信封,问共有多少种?(这是组合数学中有名的错位问题。著名数学家伯努利(Bernoulli)曾最先考虑此题。后来,欧拉对此题产生了兴趣,称此题是“组合理论的一个妙题”,独立地解出了此题)

试编程求出完全装错情形的所有方式及其总量s。例如,输入n=3,即有3封信需要装入信封,完全装错的一种方式可以表示为312,表示第1封信装入第3个信封,第2封信装入第1个信封,第3封信装入第2个信封。对于n=3,完全装错的方式共有2种,分别是312和231.

输入

输入一个正整数n(2<=n<=6)

输出

输出完全装错情形的所有方式以及装错方式的总量s (每行输出5种方式,一行中的相邻两种方式之间用1个空格隔开。装错方式输出时,从小到大排列,见输出样例)。

样例输入

4

样例输出

2143 2341 2413 3142 3412

3421 4123 4312 4321

s=9

审题思考:

解决这道题目可以利用DFS。

参考过百度知道http://zhidao.baidu.com/link?url=RSi7TcgSite5RRMgHhLLGjrF12CdpX9bYL_XvyRkelMC_5HTsZY1lICYgWxNrYBNQ_xKMJWj627uiCHdgDxmYbys8g39Xdf2Bu_ji430GS7

的回答后,我对代码进行了适当解释和修改,以下是完整代码:

 //代码改编自百度知道
#include<stdio.h>
#include<stdlib.h>
int printed,ti=;
void draw(int* a,int k)//输出函数
{
int i;
for(i=;i<k;i++)
printf("%d",a[i]);
printf(" ");
if(ti!=)
ti++;
if(ti==)
{
printf("\n");
ti=;
}
}
void Settle(int *a,int iStep,int k)//函数调用值为:a,0,k
{
int i;
for(i=;i<iStep-;i++)
if(a[iStep-]==a[i]) return;//判断序号为(iStep-1)的信是否同序号为i的信放入同一个信箱,否则将函数返回
if(iStep==k)
{
draw(a,k);
printed++;
}
for(i=;i<=k;i++)
{
if(i==iStep+) continue;//判断序号i信是否放到的是i信封,是则继续下一步
a[iStep]=i;
Settle(a,iStep+,k);
}
}
int main()
{
int* a;
int k;
scanf("%d",&k);
a=(int*)calloc(k,sizeof(int));//在内存的动态存储区中分配k个长度为int大小的连续空间
Settle(a,,k);
printf("\ns=%d",printed);
return ;
}
  a=(int*)calloc(k,sizeof(int));//在内存的动态存储区中分配k个长度为int大小的连续空间
 /*
它的意思是a为一块长度为k*sizeof(int) = 4k的内存块,可以存储k个int型变量,这个内存块就相当于一个int数组
也就是等价于
int a[k]; // 注意:此时a的内存在堆栈(Stack)上面
不同的是,你所询问的这句,申请的这块内存区域是动态申请的,属于堆(heap)上面的空间。 calloc是一个C语言函数
  函数名: calloc
  void *calloc(unsigned n,unsigned size);
  功 能: 在内存的动态存储区中分配n个长度为size的连续空间,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL。
  跟malloc的区别:
  calloc在动态分配完内存后,自动初始化该内存空间为零,而malloc不初始化,里边数据是随机的垃圾数据。
  用 法: void *calloc(unsigned n,unsigned size);
  头文件:stdlib.h或malloc.h
*/

为了解读这串代码我同样在纸上运行了一小段数据帮助理解

以下是我在纸上运行的过程:

以输入N=4 为例

 

第一次调用settle函数

a : 空 空 空 空 , istep=0,k=4;

i=1,i=2,a[0]=2;

第二次调用settle函数

a : 2 空 空 空 , istep=1,k=4;

i=1,a[1]=1

第三次调用settle函数

a : 2 1 空 空 , istep=2,k=4;

i=1,a[2]=1;

第四次调用settle函数

a : 2 1 1 空 , istep=3,k=4;

i=0,i=1,a[1]=a[2]

函数返回

第三次调用settle函数

a : 2 1 空 空 , istep=2,k=4;

i=2,a[2]=2

第四次调用settle函数

a : 2 1 2 空 , istep=3,k=4;

i=0,a[2]=a[0]

函数返回

第三次调用settle函数

a : 2 1 空 空 , istep=0,k=4;

………(省略数步骤)

第五次调用settle函数

a : 2 1 4 3 , istep=4,k=4;

输出2143

经过这样的纸上运行后,发现DFS在编写程序上以递归的形式,可以通过简短的代码解题

但是面临的问题是:

1、  代码难写,不好控制终止等条件

2、  执行效率低(目前我对剪枝的原理还不是很懂)

总结:

相比于我曾经完全利用的多重FOR循环嵌套语句,DFS递归的方式更体现出了程序的严密和递归的好处

Dfs学习经验(纸上运行理解DFS)【两道题】的更多相关文章

  1. Unity3D之AssetBundle学习:Android上运行笔记

    路径统一 在Android上加载StreamingAssets文件夹下的AssetBundle文件,首先需要对加载地址进行处理,注意PC.Android和IOS的地址不一致需要针对不同的平台不同的处理 ...

  2. TQ2440学习笔记——Linux上I2C驱动的两种实现方法(1)

    作者:彭东林 邮箱:pengdonglin137@163.com 内核版本:Linux-3.14 u-boot版本:U-Boot 2015.04 硬件:TQ2440 (NorFlash:2M   Na ...

  3. ACM学习之路___HDU 5723(kruskal + dfs)

    Abandoned country Time Limit: / MS (Java/Others) Memory Limit: / K (Java/Others) Total Submission(s) ...

  4. Spark学习之在集群上运行Spark(6)

    Spark学习之在集群上运行Spark(6) 1. Spark的一个优点在于可以通过增加机器数量并使用集群模式运行,来扩展程序的计算能力. 2. Spark既能适用于专用集群,也可以适用于共享的云计算 ...

  5. leetcode 784. Letter Case Permutation——所有BFS和DFS的题目本质上都可以抽象为tree,这样方便你写代码

    Given a string S, we can transform every letter individually to be lowercase or uppercase to create ...

  6. 简单理解在Mac OS X上运行ASP.NET程序

    运行ASP.NET程序的三要素: 1) CLR(.NET运行时) 2) KRE(ASP.NET运行时) 3) Web服务器 所以在Mac OS X上运行ASP.NET程序,就需要对应这三要素的东西: ...

  7. Spark学习之在集群上运行Spark

    一.简介 Spark 的一大好处就是可以通过增加机器数量并使用集群模式运行,来扩展程序的计算能力.好在编写用于在集群上并行执行的 Spark 应用所使用的 API 跟本地单机模式下的完全一样.也就是说 ...

  8. Linux 内核学习经验总结

    Linux 内核学习经验总结 学习内核,每个人都有自己的学习方法,仁者见仁智者见智.以下是我在学习过程中总结出来的东西,对自身来说,我认为比较有效率,拿出来跟大家交流一下. 内核学习,一偏之见:疏漏难 ...

  9. 在Hadoop上运行基于RMM中文分词算法的MapReduce程序

    原文:http://xiaoxia.org/2011/12/18/map-reduce-program-of-rmm-word-count-on-hadoop/ 在Hadoop上运行基于RMM中文分词 ...

随机推荐

  1. uilabel自动换行

    NSString *str = @"我是一asdf我是一我是一我是一我是一我是一我是一我是一我是一我是一我是一我是一asdf我是一asdf我是一asdf我是一asdf我是一asdf我是一as ...

  2. HashMap允许将null用作键 也允许将null作为值

    HashMap不能保证元素的顺序,HashMap能够将键设为null,也可以将值设为null. 与之对应的是Hashtable,(注意大小写:不是HashTable),Hashtable不能将键和值设 ...

  3. cvs 文件无法上传debug

    当时文件始终上传不成功时(一般先update后commit): cvs update filename report:move away filename ,it is in the way cvs ...

  4. Day15模块(导入,使用)

    Day15模块 什么是模块: 一系列功能的集合体 一个py文件就是一个模块 aaa.py就是aaa模块 模块四种形式: 内置的模块 py文件 第三方的 包 模块三种来源:内置的,第三方,自定义 为什么 ...

  5. 我的Python分析成长之路11

    数据预处理 如何对数据进行预处理,提高数据质量,是数据分析中重要的问题. 1.数据合并 堆叠合并数据,堆叠就是简单地把两个表拼在一起,也被称为轴向链接,绑定或连接.依照轴的方向,数据堆叠可分为横向堆叠 ...

  6. Java中File类的使用介绍

    1.创建File对象的几种方式 import java.io.File; public class MyFile { public static void main(String[] args) { ...

  7. C#中何时使用dynamic

    背景:比如说,有一个方法,有很多参数,且有时候只需要其中的某几个参数,有时候需要使用全部,甚至有时候一个都不需要,这时候写一个长长的参数列表一点都不酷,且容易 出错,这时候就需要考虑C#的dynami ...

  8. EFCore CodeFirst模型迁移生成数据库备注(mysql)

    重写Mysql下sql脚本生成器 using Framework.NetCore.Extensions; using Framework.NetCore.Models; using Microsoft ...

  9. JavaScript 逗号操作符

    让我们从一个有趣的微博开始吧. 末尾的c是优先级最低的逗号操作符.逗号操作符是操作符优先级的最后一行,并且很少有文章记录,它隐藏着它的锋芒.它可能不是JavaScript强势操作符,但是我喜欢它.它简 ...

  10. struts2知识系统整理

    1.MVC 和 JSP Model 2 **   a.:M-Model 模型 包含两部分:业务数据和业务处理逻辑  b.V-View 视图:视图(View)的职责是负责显示界面和用户交互(收集用户信息 ...