340 likes | 477 Views
程序设计实习. 第十讲 习题评讲. 本讲内容. 课后作业难点题目 (2 道 ) 2009 年期中考试题 (2 道 ) 课后作业其他题目 (6 道 ) 周末上机作业选讲 (1 道 ). POJ2774 :木材加工 —— 动态规划解法. 木材厂有 N 根原木,它们的长度分别是一个整数 . 现在想把这些木头切割成 K 根长度相同的小段木头 . 小段木头的长度要求是正整数 . 请计算能够得到的小段木头的最大长度 定义函数 f(x): 要切割得到 x 根木头时 , 所能够获得的最大长度 定义函数 g(l): 从原木中最多可以切割得到 g(l) 个长为 l 的木头
E N D
程序设计实习 第十讲 习题评讲
本讲内容 • 课后作业难点题目(2道) • 2009年期中考试题(2道) • 课后作业其他题目(6道 ) • 周末上机作业选讲(1道)
POJ2774:木材加工——动态规划解法 • 木材厂有N根原木,它们的长度分别是一个整数. 现在想把这些木头切割成K根长度相同的小段木头.小段木头的长度要求是正整数. 请计算能够得到的小段木头的最大长度 • 定义函数f(x):要切割得到x根木头时,所能够获得的最大长度 定义函数g(l):从原木中最多可以切割得到g(l)个长为l的木头 则f单调减,g单调减。 (为什么?f,g是互为反函数吗?) (注意g函数的单调性是二分法的基础,即求最大的x,使得g(x)=K。) • k -> len -> maxK -> maxRest 要切割出k段 -> len = f(k) -> maxK = g(len) -> 切完maxK根长len的木头后剩下最长木头的长度是maxRest 其中有maxRest < len成立。(为什么?)
int sourceBar[N]: 存储N根原木的长度 • objectBar[K]:结构体 objectBar[i]的含义如下 • len;要切割得到i根木头时,所能够获得的木头的最大长度 • maxK; 切割成长度为len的木头时, 可切得的最大木头数量 • MaxRest;切割成maxK根长度为len的木头后, 剩余原木的最大长度 注意,先确定len,再确定maxK,最后确定MaxRest。 • total: 切割前N根原木的长度总和 • 特殊情况:K>total : 无法切割 • int partition( int k) • k==1: 终态, objectBar[1].len为切割前最长原木的长度 objectBar[1].maxK为长度为len的原木个数 objectBar[1].MaxRest为长度小于len的最长原木长度 • objectBar[k-1].maxK=0 : 递归过程partition(k-1) • objectBar[k-1].maxK>=k: objectBar [k]与objectBar [k-1]相同 • objectBar[k-1].maxK<k: • 确定len:objectBara[k].len[objectBar[k-1].MaxRest, objectBar[k-1].len) • 哪个长度len能够使得objectBar[k].maxKk?(也就是g(len)>=k) • 不要忘了计算objectBar[k].MaxRest
木材加工参考程序 #include <iostream> #include <algorithm> using namespace std; const int MAX = 10005; int srcLen[MAX]; // 原始木头长度 int N,K,tot; struct{ int len; int maxK; int maxrest; }objectBar[MAX];
void solve() { if(tot < K){ // 特殊情况特殊判断 printf("0\n"); return; } int i,j,k,cnt,rest; //对递推开始状态初始化 for(i = N; i >= 1 && srcLen[i] == srcLen[N]; i--); objectBar[1].len = srcLen[N]; objectBar[1].maxK = N - i; objectBar[1].maxrest = srcLen[i]; //开始递推
//开始递推 for(k = 2; k <= K; k++) { if(objectBar[k - 1].maxK >= k){ objectBar[k].len = objectBar[k - 1].len; objectBar[k].maxK = objectBar[k - 1].maxK; objectBar[k].maxrest = objectBar[k - 1].maxrest; } else{
for(j = objectBar[k - 1].len - 1; j >= objectBar[k - 1].maxrest && j >= 1; j--) { // 枚举可能长度j cnt = 0,rest = 0; for(i = N; i >= 1; i--){ //计算最多可以切出多少个长度为j的木头 if(srcLen[i] >= j){ cnt += srcLen[i] / j; rest = rest > (srcLen[i] % j) ? rest : (srcLen[i] % j); } else{ rest = rest > srcLen[i] ? rest : srcLen[i]; break; } } if(cnt >= k) break; }
objectBar[k].len = j; objectBar[k].maxK = cnt; objectBar[k].maxrest = rest; } } printf("%d\n",objectBar[K].len); }
int main() { freopen("f.txt","r",stdin); scanf("%d%d",&N,&K); tot = 0; for(int i = 1; i <= N; i ++){ scanf("%d",&srcLen[i]); tot += srcLen[i]; } sort(srcLen,srcLen + N + 1); solve(); return 0; }
POJ2787 :算24 • poj2787 算24 • 给出4个小于10个正整数,你可以使用加减乘除4种运算以及括号把这4个数连接起来得到一个表达式。现在的问题是,是否存在一种方式使得得到的表达式的结果等于24 • 每一种可能的计算方式都对应一棵语法树。比如a*(b+c)。本题只有4个数、4中运算,因此可以通过穷举搜索全部可能的语法树(因而穷举了全部可能表达式)来寻找24。
穷举搜索的方法是,从语法树的树叶开始往上形成语法树,穷举搜索的方法是,从语法树的树叶开始往上形成语法树, 即自底向上的构建语法树。 • 具体的说,对当前的n(>1)个数,任意取2个数x,y(共有 C(n,2)种),然后枚举它们之间的6种可能运算: x + y , x – y , y – x, x * y ,x / y (y ≠ 0), y / x (x ≠ 0 ) • 设z为x,y运算的结果,那么用z替代原来的x,y,n个数就变成为了n – 1个数。递归进行这个过程,直到n = 1。 #include <stdio.h> #define up 24.00001 #define down 23.99999 bool is(int n); double a[5][4]; //a[i]用于存放求解i个数时的各数
int main() { while(scanf("%lf %lf %lf %lf", &a[4][0], &a[4][1], &a[4][2], &a[4][3]) && a[4][0]!=0) { if (is(4)) printf("YES\n"); else printf("NO\n"); } } bool is(int n) { if (n == 1) return (a[1][0] < up && a[1][0] > down); // 到达终止条件,判断是否找到24 double * pa = a[n]; double * pb = a[n-1];
for (int i=0; i<n; i++) { for (int j=i+1; j<n; j++) { //将除了pa[i], pa[j]以外的复制给下层函数。 int iter = 0; for (int temp=0; temp<n; temp++) { if (temp!=i && temp!=j) { pb[iter] = pa[temp]; iter++; } } //穷举对i、j的运算 pb[n-2] = pa[i] + pa[j]; //加法 if (is(n-1)) return true; pb[n-2] = pa[i] - pa[j]; //减法 if (is(n-1)) return true; pb[n-2] = pa[j] - pa[i]; //减法
if (is(n-1)) return true; pb[n-2] = pa[i] * pa[j]; //乘法 if (is(n-1)) return true; if (pa[j] != 0) //除法 { pb[n-2] = pa[i] / pa[j]; if (is(n-1)) return true; } if (pa[i] != 0) //除法 { pb[n-2] = pa[j] / pa[i]; if (is(n-1)) return true; } } } return false; }
2009年期中试题: POJ3726 仙岛求药 Description: 少年李逍遥的婶婶病了,王小虎介绍他去一趟仙灵岛,向仙女姐姐要仙丹救婶婶。叛逆但孝顺的李逍遥闯进了仙灵岛,克服了千险万难来到岛的中心,发现仙药摆在了迷阵的深处。迷阵由M×N个方格组成,有的方格内有可以瞬秒李逍遥的怪物,而有的方格内则是安全。现在李逍遥想尽快找到仙药,显然他应避开有怪物的方格,并经过最少的方格,而且那里会有神秘人物等待着他。现在要求你来帮助他实现这个目标。 图-1 显示了一个迷阵的样例及李逍遥找到仙药的路线.
Input: • 输入有多组测试数据. 每组测试数据以两个非零整数M 和N 开始,两者均不大于20。M 表示迷阵行数, N 表示迷阵列数。接下来有M 行, 每行包含N个字符,不同字符分别代表不同含义: • ‘@’:少年李逍遥所在的位置; • ‘.’:可以安全通行的方格; • ‘#’:有怪物的方格; • ‘*’:仙药所在位置。 • 当在一行中读入的是两个零时,表示输入结束。 • Output: • 对于每组测试数据,分别输出一行,该行包含李逍遥找到仙药需要穿过的最少的方格数目(计数包括初始位置的方块)。如果他不可能找到仙药, 则输出 -1。
Sample Input: 8 8 .@##...# #....#.# #.#.##.. ..#.###. #.#...#. ..###.#. ...#.*.. .#...### 6 5 .*.#. .#... ..##. ..... .#... ....@ 9 6 .#..#. .#.*.# .####. ..#... ..#... ..#... ..#... #.@.## .#..#. 0 0 Sample Output: 10 8 -1
//用宽度优先搜索解决此题。 //每次从队头取一个节点,看是否是终点,如果不是,就将队头节点周围的可达//的点都放入队列。要记住每个点的上一个点是什么 #include <iostream> using namespace std; #define SIZE 32 int M,N; char Maze[SIZE][SIZE]; int nStartR,nStartC; int nDestR,nDestC; int nHead; int nTail; struct Position { int r; int c; int depth; } Queue[SIZE * SIZE]; int Bfs();
int main() { int i,j,k; while(1) { cin >> M >> N; if( M == 0 && N == 0 ) break; memset( Maze,'#',sizeof(Maze)); for( i = 1;i <= M; i ++ ) for( j = 1; j <= N; j ++ ) { cin >> Maze[i][j]; if( Maze[i][j] == '@' ) { nStartR = i; nStartC = j; Maze[i][j] = '.'; } else if( Maze[i][j] == '*' ) { nDestR = i; nDestC = j; Maze[i][j] = '.'; } }
cout << Bfs() << endl; } } int Bfs( ) { nHead = 0; nTail = 1; Queue[nHead].r = nStartR; Queue[nHead].c = nStartC; Queue[nHead].depth = 0; int dir[][2] = {{0,1},{0,-1},{1,0},{-1,0}}; while( nHead != nTail) { if( Queue[nHead].r == nDestR && Queue[nHead].c == nDestC ) { return Queue[nHead].depth; }
for(int i = 0; i < 4; i++) { int nCurR = Queue[nHead].r + dir[i][0]; int nCurC = Queue[nHead].c + dir[i][1]; if(Maze[nCurR][nCurC] == '.') { Queue[nTail].c = nCurC; Queue[nTail].r = nCurR; Queue[nTail].depth = Queue[nHead].depth + 1; Maze[nCurR][nCurC] = '#'; nTail ++; } else { if(Maze[nCurR][nCurC] == '*') return Queue[nHead].depth + 1; } } nHead ++; } return -1; }
2009年期中试题: POJ3728 Blah数集 Description: 大数学家高斯小时候偶然间发现一种有趣的自然数集合Blah,对于以a为基的集合Ba定义如下: (1) a是集合Ba的基,且a是Ba的第一个元素; (2)如果x在集合Ba中,则2x+1和3x+1也都在集合Ba中; (3)没有其他元素在集合Ba中了。 现在小高斯想知道如果将集合Ba中元素按照升序排列,第N个元素会是多少? Input: 输入包括很多行,每行输入包括两个数字,集合的基a(1<=a<=50))以及所求元素序号n(1<=n<=1000000) Output: 对于每个输入,输出集合Ba的第n个元素值 Sample Input 1 10028 5437 Sample Output 418900585
#include <iostream> using namespace std; int Stack[1000010]; int main() { int a,n; while( scanf("%d%d",&a,&n) != EOF) { int p2 = 1 ,p3 = 1; int nTop = 1; Stack[1] = a; while(1) { if( nTop == n ) { printf("%d\n", Stack[nTop] ); break; } if( 2 * Stack[p2] + 1 < 3 * Stack[p3]+1 ) { nTop ++; Stack[nTop] = 2 * Stack[p2] + 1; p2 ++; }
else if( 2 * Stack[p2] + 1 > 3 * Stack[p3]+1 ) { nTop ++; Stack[nTop] = 3 * Stack[p3] + 1; p3 ++; } else { // 扩展的结点相等的时候两个指针都要向前滑动 nTop ++; Stack[nTop] = 3 * Stack[p3] + 1; p3 ++; p2 ++; } } } return 0; }
POJ2804:字典 • 词条(一行):一个英文单词、一个外语单词 • 100000 个词条 • 给外语单词,翻译出英文 • 这个题目很简单,方法较多,可以 (1)排序+二分 (2)hash表 (3)(平衡)二叉搜索树 (4)Trie树 • 关键点:查找的效率。以方法(1)为例: • qsort → bsearch • 使用结构表示词条 • 外语单词 • 英文单词 • 外文单词作为排序的key值 • 搜索词条:外语单词 • 注意: qsort排序的元素类型与bsearch查找的元素类型要完全一致
POJ2797:最短前缀 • 前缀:单词前若干个字母,不是其它单词的前缀 • 2~1000行 • 可以是整个单词 • 这个题目关键点 • 把有相同前缀的单词放到一起: qsort • 确定它们的各自前缀,只需考虑相邻的单词(为什么?证明) • 不是前面单词的前缀 • 不是后面单词的前缀 • 按照输入的顺序输出: qsort • 时间复杂度O(nlogn) • 两次排序,使用结构保存: • 单词本身word • 单词word的前缀prefix • word在输入中的顺序
POJ2813:画家问题 • 分析 • 问题特征: (1) 每一格要么画一次,要么不画 (2) 操作顺序无关性,只与在该格子上的操作次数有关 • 猜测:确定第一行的每一块是否要画,就可以确定其他全部格子是否要画。 • 可能无解(无法将全部块涂成黄色) • 技巧 • 加边框,简化计算 • 使用位运算,枚举第一行的全部情况 • 0 ~ (1<<N – 1)种情况 • 第k位为1,画第一行的第k块
POJ2814:拨钟问题 • 与熄灯问题类似 • 每一种移动,最多可出现3次 • 猜测:确定第一行(移动1,2,3)的移动分别出现0~3次 • 余下的行中,各移动的次数就决定了 • 不同之处:第一行的各元素有4种取值(熄灯问题中只有两种) 1 2 3 4 5 6 7 8 9
POJ2815:城堡问题 • 与黑瓷砖上行走问题类似:枚举+递归 • 枚举:站在任何一点,开始游走 • 递归:从当前点往其他地方走,不撞南墙不回头 • 小技巧:四个方向数字1,2,4,8刚好适合做位运算
POJ1088:滑雪 • 从下往上逆行 • 按高度排序:qsort • 在A点:已经走了X步 • 周围点走,能走到B:从B已经可以滑Y步 • X+1>Y :从B可以滑X+1步 • 定义合适的结构 • 高度 • X坐标 • Y坐标 • 可向下滑的最大步骤 • 走到A,找B:bseach • A的高度+1 • A的前、后、左、右
POJ2766:最大子矩阵 • 题目描述 已知矩阵的大小定义为矩阵中所有元素的和。给定一个矩阵,你的任务是找到最大的非空(大小至少是1 * 1)子矩阵。比如,如下4 * 4的矩阵0 -2 -7 09 2 -6 2-4 1 -4 1-1 8 0 -2的最大子矩阵是9 2-4 1-1 8这个子矩阵的大小是15。
解题思路 • 首先考虑一维的情况: 数列a1,a2,…,an,它的子数列定义为 s(i,j)=ai+…+aj。求最大子数列。 设b(j)=max{s(i,j),i=1,2,…,j}。那么有递推方程b(j)=max{b(j-1)+aj,aj} • 然后考虑二维降一维的方法: arr[][]为原矩阵,能够把arr的第i行到第j行压缩?可以! 设a[k]=arr[i][k]+arr[i+1][k]+…+arr[j][k],k=1,2,…,n。那么a是arr第i行到第j行子矩阵的压缩。
解题思路 • 时间复杂度是O(n^3) • 课后思考题:如果一个三维的数据立方体arr[][][],如果求它的最大子立方体?