1 / 63

程序设计实习

程序设计实习. 习题课. 枚举. 枚举 : 在条件范围内对相关变量依次取值,列出所有可能的情况,逐一检查是否是问题的解 关键: 有序地枚举解空间,不漏掉情况 尽早发现不是解的情况 枚举中的优化. 搜索:复杂的、高级的枚举. 搜索 : 解空间的每个元素是一个动作的序列 F 将初态 S 0 变换为另一个状态 F(S 0 ) 如果 F(S 0 ) 是符合要求的 S*, 那么 F 是真解 , 否则是伪解 按照一定规则 , 确定在每个状态 S 下 , 分别有哪些动作可供选择 深度优先搜索,广度优先搜索 需要对遍历过的状态进行标记

denver
Download Presentation

程序设计实习

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 程序设计实习 习题课

  2. 枚举 • 枚举:在条件范围内对相关变量依次取值,列出所有可能的情况,逐一检查是否是问题的解 • 关键: • 有序地枚举解空间,不漏掉情况 • 尽早发现不是解的情况 • 枚举中的优化

  3. 搜索:复杂的、高级的枚举 • 搜索: 解空间的每个元素是一个动作的序列F • 将初态S0变换为另一个状态F(S0) • 如果F(S0)是符合要求的S*, 那么F是真解, 否则是伪解 • 按照一定规则, 确定在每个状态S下,分别有哪些动作可供选择 • 深度优先搜索,广度优先搜索 • 需要对遍历过的状态进行标记 • 采用递归的办法, 产生每个动作序列 • 递归的出口:达到目标状态;状态已经遍历过

  4. 递归 • 递归: 从目标出发,把一个问题逐级分解成子问题 • 子问题与原问题之间是纵向的、同类的关系 • 语法形式上:在一个函数的运行过程中,调用这个函数自己 • 直接调用:在fun()中直接执行fun() • 间接调用:在fun1()中执行fun2();在fun2()中又执行fun1() • 关键: • 递推关系 • 终止条件 • 递归算法思路清晰,程序易读易写

  5. 动态规划 • 动态规划:采用自底向上的递推算法 • 从已知条件出发,将计算出的结果保存起来,直接用于后续计算。 • 避免树形递归的大量重复运算。 • 同样需要寻找递推关系

  6. 小结 • 根据题意,以及自己对方法的熟悉程度,灵活选择各种方法,综合使用 • 直接解决 • 枚举,(搜索) • 递归,转化为规模更小的同类问题 • 遇到具有多阶段过程的问题,先走一步试试,考虑如何将问题转化(如考虑第一步或最后一步) • 难的不会想简单的

  7. poj 2766 最大子矩阵 • 题目描述 已知矩阵的大小定义为矩阵中所有元素的和。给定一个矩阵,你的任务是找到最大的非空(大小至少是1 * 1)子矩阵。 • 比如,如下4 * 4的矩阵:0 -2 -7 0 9 2 -6 2-4 1 -4 1-1 8 0 -2 其最大子矩阵是:9 2-4 1-1 8 这个子矩阵的大小是15。

  8. poj 2766 最大子矩阵 • 输入 输入是一个N * N的矩阵。输入的第一行给出N (0 < N <= 100)。再后面的若干行中,依次(首先从左到右给出第一行的N个整数,再从左到右给出第二行的N个整数……)给出矩阵中的N2个整数,整数之间由空白字符分隔(空格或者空行)。已知矩阵中整数的范围都在[-127, 127]。 • 输出 输出最大子矩阵的大小。

  9. poj 2766 最大子矩阵 • 样例输入 4 0 -2 -7 0 9 2 -6 2 -4 1 -4 1 -1 8 0 -2 • 样例输出 15

  10. 解题思路 • 枚举:依次计算每个子矩阵的值。 i、j分别为子矩阵的纵坐标。i=0时: 0 -2 -7 0 9 2 -6 2-4 1 -4 1-1 8 0 -2 b[N]:存放子矩阵每列的和 则问题转换为寻找b[N]的最大子序列。

  11. 参考程序 for(int i = 0; i < N; i++){//遍历子矩阵,i、j为子矩阵的 //两个纵坐标 memset(b,0,sizeof(b)); //每次均初始化b数组 for(int j = i; j < N; j++){ for(int k = 0; k < N; k++){//压缩数组,将求最大子矩阵 //转换为求b的最大子序列问题 b[k] += arr[j][k]; } int tempMax = maxLine(b, N); if(tempMax > result) result = tempMax; } } printf("%d\n",result);

  12. 参考程序 int maxLine(int a[], int n){ int m = -10000, s = 0; for (i = 0; i < n; i++){//依次计算从序列中的i个元素开始的 //最大子序列值 if (s < 0){ s = 0; } s += a[i]; if (m < s) m = s; } return m; }

  13. poj 2950 摘花生 • 题目描述 • 鲁宾逊先生有一只宠物猴,名叫多多。这天,他们两个正沿着乡间小路散步,突然发现路边的告示牌上贴着一张小小的纸条:“欢迎免费品尝我种的花生!——熊字”。鲁宾逊先生和多多都很开心,因为花生正是他们的最爱。在告示牌背后,路边真的有一块花生田,花生植株整齐地排列成矩形网格(如图1)。有经验的多多一眼就能看出,每棵花生植株下的花生有多少。为了训练多多的算术,鲁宾逊先生说:“你先找出花生最多的植株,去采摘它的花生;然后再找出剩下的植株里花生最多的,去采摘它的花生;依此类推,不过你一定要在我限定的时间内回到路边。”

  14. poj 2950 摘花生 • 我们假定多多在每个单位时间内,可以做下列四件事情中的一件:1) 从路边跳到最靠近路边(即第一行)的某棵花生植株;2) 从一棵植株跳到前后左右与之相邻的另一棵植株;3) 采摘一棵植株下的花生;4) 从最靠近路边(即第一行)的某棵花生植株跳回路边。 • 现在给定一块花生田的大小和花生的分布,请问在限定时间内,多多最多可以采到多少个花生?注意可能只有部分植株下面长有花生,假设这些植株下的花生个数各不相同。

  15. poj 2950 摘花生 • 例如在图2所示的花生田里,只有位于(2, 5), (3, 7), (4, 2), (5, 4)的植株下长有花生,个数分别为13, 7, 15, 9。沿着图示的路线,多多在21个单位时间内,最多可以采到37个花生。

  16. poj 2950 摘花生 • 输入 输入的第一行包括一个整数T,表示数据组数每组输入的第一行包括三个整数,M, N和K,用空格隔开;表示花生田的大小为M * N(1 <= M, N <= 50),多多采花生的限定时间为K(0 <= K <= 1000)个单位时间。接下来的M行,每行包括N个非负整数,也用空格隔开;第i + 1行的第j个整数Pij(0 <= Pij <= 500)表示花生田里植株(i, j)下花生的数目,0表示该植株下没有花生。 • 输出 输出包括T行,每一行只包含一个整数,即在限定时间内,多多最多可以采到花生的个数。

  17. 解题思路 • 如何存储每棵植株 • 可用结构数组 struct plant{ int x; int y; int num; }plant[2501]; • 对每棵植株按照花生多少进行排序 • 依次判断是否够时间摘下每棵植株上的花生

  18. 参考程序 #include<iostream> using namespace std; struct huasheng{ int x; int y; int num; }hs[2501]; int compare(const void* a,const void* b){ struct huasheng *w = (struct huasheng *) a; struct huasheng *v = (struct huasheng *) b; return v->num - w->num; }

  19. int main(){ int T; scanf("%d",&T); while(T --){ int M,N,K; scanf("%d%d%d",&M,&N,&K); int num = M*N; int i = 0,j = 0,k = 0; while(num --){ scanf("%d",&hs[k].num); hs[k].x = i; hs[k].y = j / N; i = (i + 1) % N; j ++; k ++; } qsort(hs,M*N,sizeof(huasheng),compare);

  20. int sum = 0,time = 0, l = 0; while(time < K){ if(hs[l].num == 0||l >= M*N) break; if(l == 0) time = time + 2*(hs[l].y + 1) + 1; else time = time + abs(hs[l].x - hs[l - 1].x) + abs(hs[l].y - hs[l - 1].y) + 1 + hs[l].y + 1; if(time > K) break; else{ sum += hs[l].num; time -= (hs[l].y + 1); } l ++; } printf("%d\n",sum); } }

  21. poj 2788 二叉树 • 由正整数1,2,3……组成了一颗二叉树。我们已知这个二叉树的最后一个结点是n。现在的问题是,结点m所在的子树中一共包括多少个结点。 • 比如,n = 12,m = 3那么上图中的结点13,14,15以及后面的结点都是不存在的,结点m所在子树中包括的结点有3,6,7,12,因此结点m的所在子树中共有4个结点。

  22. poj 2788 二叉树 • 输入 输入数据包括多行,每行给出一组测试数据,包括两个整数m,n (1 <= m <= n <= 1000000000)。最后一组测试数据中包括两个0,表示输入的结束,这组数据不用处理。 • 输出 对于每一组测试数据,输出一行,该行包含一个整数,给出结点m所在子树中包括的结点的数目。

  23. 解题思路 • 此题有多种做法 • 一种做法是:从m点出发,逐次计算其子树第k层的最小节点minNode和最大节点maxNode,判断节点n是否在m子树的第k层中,如果n>maxNode,则继续计算节点第k+1层,否则分n<minNode和n>=minNode两种情况,计算m子树总结点数。

  24. int main(){ int m=0,n=0; while(scanf("%d %d",&m,&n) != EOF){ if (m == 0 && n == 0) break; int minNode=m, maxNode=m, num=1; while(1){ minNode = minNode * 2; maxNode = maxNode * 2 + 1; if (n <= maxNode) { if (minNode <= n) num += (n - minNode) + 1; break; } num += maxNode - minNode +1; } printf("%d\n",num); } return 0;}

  25. int main(){ int m,n,x,i,ans; cin >> m >> n; while (1){ if (m==0 && n==0) break; x = m; i = 0; while (x <= n){ x = (x<<1) + 1; i++; } if (n < (m<<i)) ans = (1<<i) - 1; else ans=(1<<i)+(n - (m<<i)); cout << ans << endl; cin >> m>> n; } return 0; }

  26. poj 1095 Trees Made to Order • 题目描述 We can number binary trees using the following scheme:  • The empty tree is numbered 0. • The single-node tree is numbered 1. • All binary trees having m nodes have numbers less than all those having m+1 nodes. • Any binary tree having m nodes with left and right subtrees L and R is numbered n such that all trees having m nodes numbered > n have eitherLeft subtrees numbered higher than L, orA left subtree = L and a right subtree numbered higher than R.

  27. poj 1095 Trees Made to Order • The first 10 binary trees and tree number 20 in this sequence are shown below: • Your job for this problem is to output a binary tree when given its order number.

  28. poj 1095 Trees Made to Order • 输入 Input consists of multiple problem instances. Each instance consists of a single integer n, where 1 <= n <= 500,000,000. A value of n = 0 terminates input. (Note that this means you will never have to output the empty tree.) • 输出 For each problem instance, you should output one line containing the tree corresponding to the order number for that instance. To print out the tree, use the following scheme:A tree with no children should be output as X.A tree with left and right subtrees L and R should be output as (L')X(R'), where L' and R' are the representations of L and R.If L is empty, just output X(R').If R is empty, just output (L')X.

  29. poj 1095 Trees Made to Order • 样例输入 1 20 31117532 0 • 样例输出 X ((X)X(X))X (X(X(((X(X))X(X))X(X))))X(((X((X)X((X)X)))X)X)

  30. 解题思路 • tNum[i]:表示有i个node的不同树的个数; • tNum[0]=1; tNum[1]=1; tNum[2]=2; • tLast[j]:表示最后一棵有j个node的树的编号; • tLast[0]=0; tLast[1]=1; tLast[2]=3; • 可以得到递推关系: tNum[i]=∑tNum[k]*tNum[i-1-k],(k = 0,1,…,i-1) tLast[j] = tLast[j-1]+ tNum[j]

  31. #include<iostream> using namespace std; int tNum[20]; int tLast[20]; void print(int n);//输出编号为n的子树 int main(){ int n; tNum[0] = 1; tNum[1] = 1; tLast[0] = 0; tLast[1] = 1; for(int i = 2; i < 20; i++){ tNum[i] = 0; for(int j = 0; j < i; j++) { tNum[i] += tNum[j] * tNum[i-j-1]; } tLast[i] = tLast[i-1] + tNum[i]; } while(scanf("%d",&n) && n){ print(n); printf("\n"); } return 0; }

  32. void print(int n) {//输出编号为n的树 int i,j,t; for(j=1;;j++){ //找到n对应的树有几个节点:j if( n <= tLast[j] ) break; } n = n - tFirst[j-1]; for(i = 0; i < j; i++){ //找到其左子树有几个节点:i t = tNum[i]*tNum[j-1-i]; if(n > t) n = n - t; else break; } if(i != 0){ printf("("); print(tLast[i-1] + 1 + (n-1)/tNum[j-1-i]); printf(")"); } printf("X"); if(i != j-1){ printf("("); print(tLast[j-2-i] + 1 + (n-1)%tNum[j-1-i]); printf(")"); } }

  33. poj 2804:字典 • 词条(一行):一个英文单词、一个外语单词 • 100000 个词条 • 给外语单词,翻译出英文 • 关键点:查找的效率 • qsort,bsearch • 使用结构表示词条 • 外语单词 • 英文单词 • 搜索词条:外语单词 • 注意: qsort排序的元素类型与bsearch查找的元素类型要完全一致

  34. poj 2797:最短前缀 • 前缀:单词前若干个字母,不是其它单词的前缀 • 2~1000行 • 可以是整个单词 • 题目关键点 • 把有相同前缀的单词放到一起: qsort • 确定它们的各自前缀 • 不是前面单词的前缀 • 不是后面单词的前缀 • 按照输入的顺序输出: qsort • 注意:两次排序,使用结构体 • 单词 • 前缀 • 输入顺序

  35. poj 2815:城堡问题 • 与黑瓷砖上行走问题类似:枚举+递归 • 枚举:站在任何一点,开始游走 • 递归:从当前点往其他地方走,不撞南墙不回头 f(x,y)=1+f(x-1,y)+f(x+1,y)+f(x,y-1)+f(x,y+1)

  36. poj 2790 迷宫 • 迷宫可以看成是由n * n的格点组成,每个格点只有2种状态,.和#,前者表示可以通行后者表示不能通行。 • 当Extense处在某个格点时,他只能移动到东南西北(或者说上下左右)四个方向之一的相邻格点上。 • Extense想要从点A走到点B,问在不走出迷宫的情况下能不能办到。 • 与黑瓷砖上行走问题类似 • 递归:从A点出发,向四周走; • 走过的点要进行标记。 bool f(int x, inty); f(Ax,Ay)=f(Ax-1,Ay)||f(Ax+1,Ay)||f(Ax,Ay-1)||f(Ax,Ay+1)

  37. poj 2775 代码档案 • 样例输入 file1 file2 dir3 dir2 file1 file2 ] ] file4 dir1 ] file3 * # • 样例输出 DATA SET 1: ROOT | dir3 | | dir2 | | file1 | | file2 | dir1 file1 file2 file3 file4 输出要求:先显示目录中的子目录,然后再显示文件。 文件要求按照名字的字母表的顺序显示,目录只按照目录出现的先后显示。

  38. 递归 • 遇到文件则保存; • 遇到目录则直接输出,递归调用; • 目录结束时,对所有文件排序,按顺序输出。

  39. #include<iostream> using namespace std; void getdir(char *dirn, int n); int mycmp(const void *elem1, const void *elem2){//比较函数,文件名排序 return strcmp((char *)elem1, (char *)elem2); } int main(){ int m = 1; //m表示第几组数据 char c; while(m){ c=cin.peek(); if(c == '#') break; //先判断是否为读入结束 cout<<"DATA SET "<<m<<":"<<endl; getdir("ROOT", 0); m++; cout<<endl; cin.get(); } return 0; }

  40. void getdir(char *dirn, int n){ //输入并输出子目录,n为子目录的深度 char fname[100][100]; int k = 0, i, j; for(i = 0; i < n; i++) cout<<"| "; //先输出子目录的名字 cout<<dirn<<endl; char str[100]; while(cin>>str) { if(str[0] == ‘f’)//表示输入为一个文件 strcpy(fname[k++],str); if(str[0] == ‘d’) //表示输入的是一个新的目录,则输出目录 getdir(str, n+1); if(str[0] == ‘]’ || str[0] == ‘*’){ //表示此目录输入结束,输出 qsort(fname,k,sizeof(fname[0]),mycmp); for(i = 0; i < k; i++){ for(j = 0; j < n; j++) cout<<"| "; cout<<fname[i]<<endl; } break; } } return ; }

  41. poj 2787:算24 • 枚举:运算树,多种枚举方法 • 对树的结构、节点上的运算符、及每种树的排列进行枚举 • f(1,4):左子树A和右子树B • A:[1,3],B:[4,4] • A:[1,2],B:[3,4] • 按实际做运算的先后顺序枚举 1+(2*3-4) + - 1 * 4 2 3

  42. poj 2787:算24 • 2个数可以做6种运算: A+B、A-B、B-A 、A*B、A/B 、 B/A • 递归函数Calc(int Numbers[],n):表示对数组中的n个数进行运算,返回为1或0,1表示是可以得到24,0表示不能得到24。 • 对于Calc(int Numbers[],n),每次从数组中任取出两个数进行运算,将结果与另外n-2个数一起组成大小为n-1的数组,则问题转化成计算Calc(int Numbers[],n-1)。

  43. #include <iostream> using namespace std; double Numbers[5]; int main(){ int a[4]; while(true) { int nZeroNum = 0; for( int i = 0;i < 4;i ++ ) {//读入数组 cin >> Numbers[i]; if( Numbers[i] == 0 ) nZeroNum ++; } if( nZeroNum == 4 )//判断是否结束 break; if( Calc(4, Numbers)) cout << "YES" <<endl; else cout << "NO" << endl; } return 0; }

  44. bool Calc( double * pNumbers, int n ){ int i,j, k; double aNumber[4]; if( n == 1 ) { if( pNumbers[0] > 23.99999 && pNumbers[0] < 24.00001 ) return true; else return false; } for( i = 0;i < n - 1;i ++ ) { for( j = i + 1; j < n; j ++ ) {//从pNumbers中任取两个数 int m = 0; for( k = 0; k < n; k ++ ) {//将其全n-2个数放入新数组 if( k != i && k != j ) aNumber[m++] = pNumbers[k]; } aNumber[m] = pNumbers[i] + pNumbers[j];//对选取的两个数运算 if( Calc( aNumber, n -1 )) //并放入新数组 return true; aNumber[m] = pNumbers[i] * pNumbers[j]; if( Calc( aNumber, n -1 )) return true;

  45. aNumber[m] = pNumbers[i] - pNumbers[j]; if( Calc( aNumber, n -1 )) return true; aNumber[m] = pNumbers[j] - pNumbers[i]; if( Calc( aNumber, n -1 )) return true; if( pNumbers[i] != 0) { aNumber[m] = pNumbers[j] / pNumbers[i]; if( Calc( aNumber, n -1 )) return true; } if( pNumbers[j] != 0) { aNumber[m] = pNumbers[i] / pNumbers[j]; if( Calc( aNumber, n -1 )) return true; } }//j循环结束 }//i循环结束 return false; }

  46. poj 1088:滑雪 • 动态规划 • 按高度排序:qsort • 从高度最低的节点开始算起 数组Mlen[MaxN][MaxN]记录每个节点的最大可滑长度; Mlen[x][y]:(x,y)周围四个点中高度比它低的点的最大可滑长度+1。 Mlen[x][y] = 1 + max{ Mlen[x-1][y], Mlen[x+1][y], Mlen[x][y-1], Mlen[x][y+1]} • 节点的定义 struct Point{ int x; 注意:还需要定义h[MaxN][MaxN]; int y; int h; }pos[10010];

  47. #include<stdio.h> #include<stdlib.h> #include<memory.h> const int dx[] = {-1, 0, 0, 1}; const int dy[] = {0, -1, 1, 0}; int h[110][110] = {0}; int mLen[110][110] = {0}; struct Point{ int x; int y; int h; }pos[10010]; int mycmp(const void *elem1,const void *elem2){ Point* p1,*p2; p1=(Point*) elem1; p2=(Point*) elem2; return p1->h-p2->h; }

  48. int main(){ int row, col; scanf("%d%d", &row, &col); int k = 0; for (int i = 1; i <= row; i++) for (int j = 1; j <= col; j++){ scanf("%d", &h[i][j]); pos[k].x = i; pos[k].y = j; pos[k].h =h[i][j]; mLen[i][j] = 1; k++; } qsort(pos, row*col, sizeof(pos[0]), mycmp);

  49. for (int i = 0; i < k; i++){//递推计算mLen[x][y] int x = pos[i].x; int y = pos[i].y; int mTmp = mLen[x][y]; int tmp; for (int j = 0; j < 4; j++){ if (h[x + dx[j]][y + dy[j]] < h[x][y]){ tmp=mLen[x + dx[j]][y + dy[j]] + 1; if (tmp > mTmp ) mTmp= tmp; } } mLen[x][y] = mTmp; } int result = 0;//最大滑行长度 for (int i = 1; i <= row; i++) for (int j = 1; j <= col; j++) if (mLen[i][j] > result) result = mLen[i][j]; printf("%d\n", result); return 0; }

  50. poj 1088:滑雪 • 递归(备忘录法) • 函数MaxLen(x, y)每个点的最大可滑长度 MaxLen(x, y):(x,y)周围四个点中高度比它低的点的最大可滑长度+1。 MaxLen(x, y)= 1 + max{MaxLen(x-1, y), MaxLen(x+1, y), MaxLen(x, y-1), MaxLen(x, y+1)} • 定义数组来记录已计算过的节点的最大可滑长度

More Related