Transcript 第十讲

程序设计实习
第十讲
习题评讲
本讲内容




课后作业难点题目(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].maxKk?(也就是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 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
的最大子矩阵是
92
-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[][][],如果求它的最大子立方体?