program p4 (input , output) ; procedure rever ; var c : char ; begin read(c) ;

100
program p4(input out put) procedure rever var c char begin read(c) if c<>'!' then rev er; write(c) end begin { 主主主 } rever end 主主主主 hey! 主主主c 主主主 rever 主主主主主 主主主主主主 主主主主主主主主主主主主主主 主主 一,, 主主主主主 c 主主主主主主主主 主主主主主主主主 主主主主主主主主主主主3 3 输输 输输‘ !’ 输输输输输 输输输输输输 ,。

Upload: maddy

Post on 17-Jan-2016

29 views

Category:

Documents


1 download

DESCRIPTION

例 3 : 输入一串以 ‘ ! ’ 结束的字符,按逆序输出。. program p4 (input , output) ; procedure rever ; var c : char ; begin read(c) ; if c'!' then rever; write(c) ; end ; begin { 主程序 } rever ; end . 运行: 输入 hey! 输出 !yeh 。. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

program p4(input , output) ;procedure rever ; var c : char ; begin read(c) ; if c<>'!' then rever; write(c) ; end ;begin { 主程序 } rever ;end .运行:输入 hey!输出 !yeh 。

程序中, c 是过程 rever 的局部变量。每一次递归调用,都要为局部变量重新分配单元,因此各层的变量 c实际上是不同的量,它们分别用于保存执行本层调用时的输入值。

例例 33 :输入一串以‘ !’ 结束的字符,按逆序输出。

Page 2: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

procedure rever ; var c : char ; begin read(c) ; if c<>'!' then rever; write(c) ; end ;

hey!

c=‘h’procedure rever ; var c : char ; begin read(c) ; if c<>'!' then rever; write(c) ; end ;

procedure rever ; var c : char ; begin read(c) ; if c<>'!' then rever; write(c) ; end ;

procedure rever ; var c : char ; begin read(c) ; if c<>'!' then rever; write(c) ; end ;

c=‘e’

c=‘y’

c=‘!’

Page 3: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

程序中的递归过程图解如下:

a:=10 ;end ;

调用

a输出

调用Num(x+1);a=a+2;end;

调用

a=a+2;end;

Num(x+1); Num(x+1); Num(x+1);调用

end;

调用

end;a=a+2; a=a+2;

Num(1) Num(2) Num(3) Num(4) Num(5)

Num(1);

整个递归过程可视为由往返双向“运动”组成,先是逐层递进,逐层打开新的“篇章”,(有可能无具体计算值)当最终递进达到边界,执行完本“层”的语句,才由最末一“层”逐次返回到上“层”,每次返回均带回新的计算值,直至回到第一次由主程序调用的地方,完成对原问题的处理。

procedure num(x: integer ) begin if x=5 then a:=10 else begin num(x+1); a:=a+2 end end;

Page 4: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

回溯

Page 5: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

计算机求解的过程

在状态空间寻找机内解 , 可以看成是从初始状态出发 , 搜索目标状态 ( 解所在的状态 ) 的过程。

状态空间初始状态 目标状态搜索

搜索的过程可描述为: S0S1…Sn ,其中 S

0 为初态, Sn 为终态。或者说 ψ(S0) 且 φ(Sn) ,这里 ψ 称为初始条件, φ 称为终止条件。

Page 6: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

求解是状态空间的搜索 求解的过程可以描述为对状态空间的搜索

S0

S11 S12 … S1k

… … … … … …

Sn1…… Sni

…… Snm

其中 S0 为初始状态,不妨设 Sni 为终止状态

S0

Sni

问题求解就是通过搜索,寻找出一条从初始状态 S0 到终止状态 Sni 的路径。

Page 7: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

几种搜索方法状态空间的搜索实际上是一种树 /DAG ( Direct

ed Acyclic Graph )的搜索,常用的方法有: 广度优先搜索

深度优先搜索

启发式搜索

从初始状态开始,逐层地进行搜索。

从初始状态开始,逐个分枝地进行搜索。

从初始状态开始,每次选择最有可能达到终止状态的结点进行搜索。

Page 8: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

三种搜索的优劣之处 一般来说,三种搜索方法各有优劣之处: 广度优先搜索和深度优先搜索优点:一定能找到解

;缺点:时间复杂性大。 启发式搜索优点:一般来说能较快地找到解,即其

时间复杂性小;缺点:需要设计一个评价函数,并且评价函数的优劣决定了启发式搜索的优劣。

Page 9: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

当需要找出问题的解集,或者要求回答什么解是满足某些约束条件的最佳解时,往往要使用回溯法。

回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。这种方法适用于解一些组合数相当大的问题。

在问题的解空间树中,回溯法按深度优先策略,从根结点出发搜索解空间树。

算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。

如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。

回溯法

Page 10: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

回溯法的算法框架5.1.1 问题的解空间

应用回溯法解问题时,首先应明确定义问题的解空间。问题的解空间应至少包含问题的一个 ( 最优 ) 解。通常将解空间组织成树或图的形式。 问题的解向量:回溯法希望一个问题的解,能够表示成一

个 n 元式 (x1,x2,…,xn) 的形式。 显约束:对分量 xi 的取值限定 隐约束:为满足问题的解,而对不同分量之间施加的约束

。 解空间:对于问题的一个实例,解向量满足显式约束条件

的所有多元组,构成了该实例的一个解空间。 例如,对于有 n 种可选物品的 0-1 背包问题,其解空间

由长度为 n 的 0-1 向量组成。

n=3 时的 0-1 背包问题用完全二叉树表示的解空间

Page 11: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

回溯法的基本思想 扩展结点 : 一个正在产生儿子的结点 活结点 : 一个自身已生成但其儿子还没有全部生成的节点 死结点 : 一个所有儿子已经产生的结点 深度优先的问题状态生成法:如果对一个扩展结点 R ,一旦产生了它

的一个儿子 C ,就把 C 当做新的扩展结点。在完成对子树 C (以 C 为根的子树)的穷尽搜索之后,将 R 重新变成扩展结点,继续生成 R 的下一个儿子(如果存在)

宽度优先的问题状态生成法:在一个扩展结点变成死结点之前,它一直是扩展结点。

回溯法:为了避免生成那些不可能产生最佳解的问题状态,要不断地利用限界函数 (bounding function) 来处死那些实际上不可能产生所需解的活结点,以减少问题的计算量。具有限界函数的深度优先生成法称为回溯法。

Page 12: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

基本思想:

确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。

开始结点就成为一个活结点,同时也成为当前的扩展结点。

在当前扩展结点,搜索向纵深方向移至一个新结点。这个新结点就成为一个新的活结点,并成为当前扩展结点。

如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。

此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。

回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止。

Page 13: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

可重复的全排列

假设是由 1-3 组成的 3 位数program expl;

var i,j,k:integer;

begin

for i:=1 to 3 do

for j:=1 to 3 do

for k:=1 to 3 do

writeln(i,' ',j,' ',k);

end.

Page 14: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

如果是 5 位呢?

更多位呢?

不定位呢?

Page 15: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

program expl_dg;var a:array[1..10] of integer;procedure print;var i:integer;begin for i:=1 to 3 do write(a[i],' '); writeln;end;

procedure work(x:integer);var i:integer;begin if x>3 then begin print ;exit;end; for i:=1 to 3 do begin a[x]:=i; work(x+1); end;end;

begin work(1);end.

试一试,如果 n 位, n<6,

如何改动?

如果要求输出不重复的全排列呢?

3

1 2 3

1 2 3 1 3

1 2 3 1 2 1 1

1 32 2

3 2 3 2 3 1 2 3 1 2 3 1 2 13 2 3 1 2 3

Page 16: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

program expl_dg;var a:array[1..10] of integer; n:integer;procedure print;var i:integer;begin for i:=1 to n do write(a[i],' '); writeln;end;

function try(x,i:integer):boolean;var j:integer;begin try:=true; for j:=1 to x-1 do if a[j]=i then begin try:=false;break;end;end;

procedure work(x:integer);var i:integer;begin if x>n then begin print;exit;end; for i:=1 to n do if try(x,i)=true then begin a[x]:=i; work(x+1); end;end;

begin readln(n); work(1);end.

剪枝

Page 17: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

关于回溯法搜索的递归程序框架全排列算法的递归解法为例做个介绍:const n=5;var a:array [1..n] of integer;     { 记录搜索的解 }    e:array [1..n] of boolean;     { 标记数组用于记录 i 是否已经用过 }  { 初始状态都是 false}  procedure print;  var i:integer;  begin   for i:=1 to n do write(a[i],' '); end;procedure search(deep:integer);    {deep 是搜索深度 }  var i:integer;      begin    if deep>n then begin   print;                      { 输出结果 }       exit;            { 退出本层,回溯 }     end;    for i:=1 to n do      if e[i]=false then begin     { 如果 i 没有用过 , 限定条件 }        e[i]:=true;                { 标记为用过 i}          a[deep]:=i;                { 记录该位置的元素为 i}        search(deep+1);            { 搜索 deep+1 个位置上的元素 }        e[i]:=false;               { 回溯之后取消 i 元素的已用标记 }      end;  end;在很多题目中, " 限定条件 " 需要用邻接矩阵等数据表示,这个 " 限定条件 " 就是输出结果中需要满足的条件,具体的可以根据题目来修改。

Page 18: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

var i,k,n:integer; x:array[1..9] of integer;function place(k:integer):boolean; var i:integer; begin place:=true; for i:=1 to k-1 do if( )then begin place:=false; break end ; end;procedure print; var i:integer; begin for i:=1 to n do write(x[i],' '); writeln; end;

 

数字排列问题的递归实现

x[i]=x[k]

procedure try(k:integer); var i :integer; begin if( )then begin print; exit; end; for i:=1 to n do begin ( ); if( )then try(k+1) end end;begin readln(n); try(1);end.

k>n

x[k]:=iplace(k)

Page 19: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

四色问题

有形如下列图形的地图,图中每一块区域代表一个省份,现请你用红( 1 )、兰( 2 )、黄( 3 )、绿( 4 )四种颜色给这些省份填上颜色,要求每一省份用一种颜色,且任意两个相邻省份的颜色不能相同,请给出一种符合条件的填色方案。地图用无向图的形式给出,每个省份代表图上的一个顶点,边代表两个省份是相邻的。

返回到题目列表

Page 20: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

四色问题

输入70 1 0 0 0 0 11 0 1 1 1 1 10 1 0 1 0 0 00 1 1 0 1 0 00 1 0 1 0 1 00 1 0 0 1 0 11 1 0 0 0 1 0

输出1 2 1 3 1 3 4

11

2233

44

55

66

77

返回到题目列表

Page 21: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

四色问题

要得到一种可行的填色方案,比较直接的做法就是一个省一个省的填色,在填色的过程中去检查是否和周围省份的颜色存在冲突。

从第一个省份开始,对每个省份依次尝试四种颜色,判断当然选择的颜色是否和相邻的省份相同。

若都不相同,则将此省份填上当前的颜色。 若存在相同,则取下一种颜色继续尝试。 若四种颜色都不能填上,则退回上一个省份并选择下一种颜色继续尝试。

当最后一个省份也被填色后就找到一组可行解。

返回到题目列表

Page 22: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

四色问题procedure search(m: integer); // procedure search(m: integer); // 递归搜索第递归搜索第 mm 个省份个省份beginbegin if m > n then // if m > n then // 是否所有省份都已经填色是否所有省份都已经填色 begin begin write(s[1]); // write(s[1]); // 已全部填色,输出结果,终止程序已全部填色,输出结果,终止程序 for j := 2 to n do write(' ', s[j]);for j := 2 to n do write(' ', s[j]); writeln; haltwriteln; halt endend else // else // 还未全部填色还未全部填色 for j := 1 to 4 do for j := 1 to 4 do // // 依次用四种颜色对当前省份填色依次用四种颜色对当前省份填色 begin begin if if 可以 可以 then {then { 同相邻的块没有色彩冲突同相邻的块没有色彩冲突 }} begin // begin // 没有冲突没有冲突 s[m] := j; // s[m] := j; // 记录当前省份颜色记录当前省份颜色 search(m + 1); // search(m + 1); // 递归检查下一个省份递归检查下一个省份 end;end; end;end;end;end;

nn 块,块, mm 代表当前在填第几块代表当前在填第几块

Page 23: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

以一个 m*n 的长方阵表示迷宫, 0 和 1 分别表示迷宫中的通路和障碍(如下图所示)。设计一个程序,对任意设定的迷宫,如果只能按上下左右四个方向移动,求出一条从入口到出口的通路。或得出没有通路的结论。

在方阵中,允许运动的方向为上下左右,因此,对于每个状态,都有四个方向可以扩展搜索,为了简化程序,我们可以设计增量数组描述不同的运动方向。另外,为了回避边沿位置移动方向的限制(如上边沿的位置不能再向上移动等),可以对方阵进行扩充,增加一圈且有障碍。如图所示。

迷宫问题

Page 24: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

const maxm=10; maxn=10;const dx:array[1..4] of integer=(-1,1,0,0); { 上下左右方向 } dy:array[1..4] of integer=(0,0,-1,1);type address=record x,y:integer; end;var a:array[0..maxm+1,0..maxn+1] of integer;step:array[1..maxm*maxn] of address;m,n,x0,y0,x1,y1:integer;procedure init;var i,j:integer;begin readln(m,n); for i:=0 to m+1 do for j:=0 to n+1 do a[i,j]:=1; for i:=1 to m do for j:=1 to n do read(a[i,j]); readln(x0,y0); readln(x1,y1); step[1].x:=x0; step[1].y:=y0;end;procedure print(steps:integer);var i:integer;begin for i:=1 to steps-1 do write('(',step[i].x,',',step[i].y,')-->'); writeln('(',step[steps].x,',',step[steps].y,')'); halt;end;

procedure try(i:integer);var j,x,y:integer;beginfor j:=1 to 4 do begin x:=step[i-1].x+dx[j]; y:=step[i-1].y+dy[j]; if a[x,y]=0 then begin step[i].x:=x; step[i].y:=y; a[x,y]:=-1; if (x=x1) and (y=y1) then print(i) else try(i+1); a[x,y]:=0; end; end;end;begin init; a[x0,y0]:=1;try(2);end.

Page 25: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;
Page 26: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

[ 问题描述 ]

在 n×n 的国际象棋盘上,放置 n 个皇后,使任何一个皇后都不能吃掉另一个 , 要使任何一个皇后都不能吃掉另一个,需满足的条件是:同一行、同一列、同一对角线上只能有一个皇后。求放置方法 .

如 :n=4 时 , 有以下 2 种放置方法 .

*

*

*

*

*

*

*

*

N 皇后问题

输出:

2 4 1 3

3 1 4 2

Page 27: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

n 皇后问题

1 2 3

1

4

1 2 3 4 1 2 3 4 2 3 4 1 2 3 4

1 2 3 4 1 2 3 4 1 2 3 4

1 2 3 4

1 2 3 4

1 2 3 4

1 2 3 4 1 2 3 4

1 2 3 41 2 3 4

返回到题目列表

Page 28: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

n 皇后问题的解空间就应该是 1 ~ n 全排列的一部分。

解空间的长度是 n 。 解空间的组织形式是一棵 n 叉树,一个可行的解就

是从根节点到叶子节点的一条路径。 控制策略则是当前皇后与前面所有的皇后都不同列和不同对角线。

返回到题目列表

Page 29: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

var x:array[1..n] of integer; a:array[1..n] of boolean; { 列控制标志: true :可以放, false :不能放 } b:array[2..2*n] of boolean; { 左上右下方斜线控制标志, true :可以放, false :不能放 } c:array[1-n..n-1]of boolean; { 左下右上方斜线控制标志, true :可以放, false :不能放 }

初始时: fillchar(x,sizeof(x),0); fillchar(a,sizeof(a),true); fillchar(b,sizeof(b),true); fillchar(c,sizeof(c),true);

1 1 *1 2 1 3 1 4

2 1 2 2 2 3 *2 4

*3 1 3 2 3 3 3 4

4 1 4 2 *4 3 4 4

Page 30: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

procedure try(i:integer); var j:integer; begin if i=n+1 then print else for j:=1 to n do if a[j] and b[i+j] and c[i-j] then begin x[i]:=j; a[j]:=false; { 列控制标志 } b[i+j]:=false; { 左上右下方斜线控制标志 } c[i-j]:=false; { 左下右上方斜线控制标志 } try(i+1); { 如果不能递归进行,既 a[j] and b[i+j] and c[i-j]=false : 无法放置 i+1 个皇后,说明当前皇后 i 放置不正确,要回溯,消除标志 } a[j]:=true; b[i+j]:=true; c[i-j]:=true end; end;begintry(1)end.

Page 31: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

四皇后问题的递归实现

const n= 4 ;var

i,j,k:integer;   x:array[1..n] of integer;

{ 保存第 i 个皇后的列号 }function place(k:integer):boolean;  var i:integer;  begin  place:=true;  for i:=1 to k-1 do   if(x[i]=x[k])or(abs(x[i]-x[k])=abs(i-k))

then place:=false;  end;procedure print; var i:integer;  begin  for i:=1 to n do write(x[i]:4);  writeln;  end;

procedure try(k:integer); var i:integer; begin  if( ) then

begin

print;

( )

end;  for i:=( )do  begin  ( );  if place(k) then( );  end; end ;begin try(1);{ 摆放第一个皇后 }

end.

k=n+1

x[k]:=i

try(k+1)

1 to n

exit

皇后序号

摆放下一个皇后

因为从第 i 个皇后到第 i+1 个皇后的摆放方法是相同的 , 所以可以用递归的方法 .

Page 32: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

跳马问题

在 n×m 棋盘上有一中国象棋中的马:1. 马走日字;2. 马只能往右走。

请你找出一条可行路径,使得马可以从棋盘的左下角( 1 , 1 )走到右上角( n , m )。

返回到题目列表

Page 33: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

跳马问题 输入: 9 5 输出:

(1,1)->(3,2)->(5,1)->(6,3)->(7,1)->(8,3)->(9,5)

返回到题目列表

Page 34: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

跳马问题 马走日字,当马一开始在黄点时,它下一步可以到

达的点有以下的八个,但由于题目规定了只能往右走的限制,所以它只能走到四个绿色点之一。

Page 35: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

跳马问题

1. 当马一开始位于左下角的时候,根据规则,它只有两条线路可以选择(另外两条超出棋盘的范围),我们无法预知该走哪条,故任意选择一条,到达 A1 。

A

A1

A2

Page 36: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

跳马问题

2. 当到达 A1 点后,又有三条线路可以选择,于是再任意选择一条,到达 B1 。

3. 从 B1 再出发,又有两条线路可以选择,先选一条,到达 C1 。

A

A1

A2

B1

B2

B3

C1

C2

Page 37: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

跳马问题4. 从 C1 出发,可以有三条路径,选择 D1 。但到了 D1 以后

,我们无路可走且 D1 也不是最终目标点,因此,选择 D1是错误的,我们退回 C1 重新选择 D2 。同样 D2 也是错误的。再回到 C1 选择 D3 。 D3 只可以到 E1 ,但 E1 也是错误的。返回 D3 后,没有其他选择,说明 D3 也是错误的,再回到 C1 。此时 C1 不再有其他选择,故 C1 也是错误的,退回 B1 ,选择 C2 进行尝试。

AA1

A2

B1

B2

B3

C1

C2

D1

D2

D3

E1

Page 38: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

跳马问题

5. 从 C2 出发,有四条路径可以选择,选择 D4 ,从D4 出发又有两条路径,选择 E1 错误,返回 D4选择 E2 ,从 E2 出发有两条路径,先选择 F1 错误,返回 E2 选择 B ,而 B 恰好是我们要到达的目标点,至此,一条路径查找成功。

AA1

A2

B1

B2

B3

C2 E2

B

D4

E1

F1

Page 39: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

跳马问题 从上面的分析我们可以得知:

1. 在无法确定走哪条线路的时候,任选一条线路进行尝试;

2. 当从某点出发,所有可能到达的点都不能到达终点时,说明此点是一个死节点,必须回溯到上一个点,并重新选择一条新的线路进行尝试。

Page 40: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

为了描述路径,我们最直接的方法就是记录路径上所有点的坐标。但比较繁琐。

如果我对马可以走到的四个点都编上号(方向),那么我们很容易得到每一个方向上两个坐标和原位置的关系。

同时,四个方向都有了编号,那么在选择路径的时候也就可以不采用任选的方式了,只需按照编号依次尝试就可以了。

Page 41: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

1

2

3

4

增量数组:dx = (1, 2, 2, 1)

dy = (-2, -1, 1, 2)

若马所处的位置为 (x,y),则其下一步可以到达的四个位置分别是 (x+1, y-2) ,(x+2, y-1), (x+2, y+1),(x+1, y+2)。

在使用增量数组后,只要知道方向 t ,下一步的位置就是 (x+dx[t], y+dy[t]) 。

Page 42: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

为了判断棋子是否落在棋盘上,我们还必须知道当前棋子(扩展节点)的位置信息。而用一系列的方向来表示就比较麻烦,因此,我们另外使用两个变量来保存当前棋子的坐标。但这时千万要注意的是一定要在回溯的时候,修改当前棋子的坐标。

跳马问题的解空间是从左下角到右上角的所有路径。 解空间的长度是所有路径中最长的路径的长度。 解空间的组织形式是一棵四叉树,一个可行的解就是从根节

点到叶子节点的一条路径。 控制策略则是马必须在棋盘内。

Page 43: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

分析:1、马跳的方向:

x:array[1..4,1..2]of integer=

((1,-2),(2,-1),(2,1),(1,2));

4 个方向横向和纵向的增量。

2 、记录马经过的位置坐标

a:array[1..16,1..2]of integer;

第 i 步所在的位置, 1 :横坐标

2 :纵坐标

3 、马的当前位置:( a[I,1],a[I,2] )

下一个位置可能是:

(a[I,1]+x[j,1],a[I,2]+x[j,2]) 1<=j<=4

4 、目标: a[I,1]=n;a[I,2]=m;

Page 44: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

const maxx=15; maxy=15; x:array[1..4,1..2]of integer=((1,-2),(2,-1),(2,1),(1,2)); {4 个方向 }var n,m,t:integer; a:array[1..maxx+1,1..2]of integer;{ 记录走的路径坐标 }procedure print(i:integer); var j:integer; begin inc(t); write(t,':'); for j:=1 to i do write('(',a[j,1],' ',a[j,2],')'); writeln; end;

Page 45: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

procedure try(i:integer); { 搜索到当前第 i 个点 } var j:integer; begin if (a[i,1]=n) and (a[i,2]=m) then print(i); for j:=1 to 4 do if (a[i,1]+x[j,1]>=0)and(a[i,1]+x[j,1]<=n) and(a[i,2]+x[j,2]>=0)and(a[i,2]+x[j,2]<=m){ 判界 } then begin a[i+1,1]:=a[i,1]+x[j,1]; a[i+1,2]:=a[i,2]+x[j,2]; try(i+1); end; end;begin assign(output,'house1.out'); rewrite(output); readln(n,m); t:=0; a[1,1]:=0;{ 起始位置作为第 1 个点 ,x,y 为 0} a[1,2]:=0; try(1); close(output);end.

Page 46: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

求最少步到达 B 点。

Best: 最短路线, a :临时得到的一个路线。 Min :最少步。procedure try(i:integer); { 搜索到当前第 i 个点 } var j:integer; begin if ((a[i,1]=n) and (a[i,2]=m))and(i<min) then begin min:=i;best:=a;exit;end; { 记下当前最短路径和最少步数 } if ((a[i,1]<>n) or (a[i,2]<>m))and(i>=min) then exit; { 剪枝优化 } for j:=1 to 4 do if (a[i,1]+x[j,1]>=0)and(a[i,1]+x[j,1]<=n) and(a[i,2]+x[j,2]>=0)and(a[i,2]+x[j,2]<=m) then begin a[i+1,1]:=a[i,1]+x[j,1]; a[i+1,2]:=a[i,2]+x[j,2]; try(i+1); end; end;

19 19

Page 47: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

跳马问题(思考)

1. 在本例中,我们只要求输出一条可行路径,如果我们需要输出所有的可行路径,怎样改动此程序呢?

2. 如果我们把问题改成“在 n×m 的棋盘上有一骑士,开始时,骑士在点 (x,y) ,现在请你找出一条可行的路径,使得骑士可以不重复的走遍棋盘上的每一个点。”的话,又要如何编写此程序呢?

Page 48: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

0,1 背包问题 已知一个容量大小为 M 重量的背包和 N 种物品

,每种物品的重量为 Wi 。若将物品放入背包将得到 Pi 的效益,求怎样选取物品将得到效益最大

返回到题目列表

输入:第一行一个整数,为背包的容量 M ;第二行一个整数,为物品的种数N; 第三行N个整数为各物品的重量;第四行N个整数分别为N个物品的价值输出:第一行为最大总价值;第二行为装入的各物品的重量(未装的物品用0); 第三行为装入的各物品的价值 (未装的物品用0)

Page 49: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

算法分析

本题可以用递归求解:设当前有 N 个物品,容量为 M ;因为这些物品要么选,要么不选,我们假设选的第一个物品编号为 I ( 1~I-1 号物品不选),问题又可以转化为有 N-I 个物品(即第 I+1~N 号物品),容量为 M-Wi 的子问题……如此反复下去,然后在所有可行解中选一个效益最大的便可。

另外,为了优化程序,我们定义一个函数如下: F[I] 表示选第 I~N 个物品能得到的总效益。不难推出: F[N]=Pn F[I]=F[I+1]+Pi ( I=1…N-1 ) 假设当前已选到第 I 号物品,如果当前搜索的效益值 +F[I+1] 的值仍

然比当前的最优值小,则没有必要继续搜索下去。

返回到题目列表

Page 50: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

算法框架procedure search(i:integer; j:byte); { 递归求解 }var k:byte;begin if now+f[j]<=ans then exit; { 如果没有必要搜索下去 } if now>ans then begin { 修改最优解 } ans:=now; out:=ou; end; for k:=j to n do { 选取物品 } if w[k]<=i then begin now:=now+p[k]; ou[k]:=true; search(i-w[k],k+1); now:=now-p[k]; ou[k]:=false; end;end;

Page 51: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

某乡有 n 个村庄 (1<n<40) ,有一个售货员,他要到各个村庄去售货,各村庄之间的路程 s(0<s<1000) 是已知的,且 A 村到 B 村与 B 村到 A 村的路大多不同。为了提高效率,他从商店出发到每个村庄一次,然后返回商店所在的村,假设商店所在的村庄为 1 ,他不知道选择什么样的路线才能使所走的路程最短。请你帮他选择一条最短的路。

输入 : 村庄数 n 和各村之间的路程 ( 均是整数 ) 。

输出 : 最短的路程。

样例输入 :

3 { 村庄数 }

0 2 l { 村庄 1 到各村的路程 }

1 0 2 { 村庄 2 到各村的路程 }

2 1 0 { 村庄 3 到各村的路程 }

样例输出 :

3

售货员的难题

Page 52: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

算法分析:

题目给定的村庄数不多 (0<40) ,所以可以用回溯的方法,从起点出发找出所有经过其他各村庄的回路,计算其中的最短路程。用一个过程 road(step , line : byte) 来描述走的状况,其中 step 是当前已到过的村庄数、 line 是当前所在的村庄。如果 step = n ,接下去只能回起点了,此时看第 line 个村庄到起点的路程加上已走的总路程,如果它比最小值还小则替换最小值。如果 step 还小于 n ,那么将还没有到过的村庄一个一个地试过去,再调用下一步 road(step+1 ,新到的村庄号 ) 。

Page 53: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

var a:array[1..40,1..40] of integer; n,i,j:integer; min,m:longint; bj:array[1..40] of boolean;

begin readln(n); for i:=1 to n do for j:=1 to n do read(a[i,j]); fillchar(bj,sizeof(bj),true); min:=99999999; m:=0; road(1,1); writeln(min);end.

procedure road(step,line:byte); var i,j,k:byte; begin if ( ) then begin if m+a[line,1]<min then min:=m+a[line,1]; exit; end; for i:=2 to n do if(i<>line)and ( ) then begin m:=m+a[line,i]; bj[line]:=false; if m<min then ( ); m:=m-a[line,i]; ( ) ; end; end;

step=n

bj[i]

road(step+1,i)

bj[line]:=true

满足最优性要求

剪枝

恢复其递归前的值

Page 54: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

计算拆分方案

给定一个正整数N,假设 0<A1<=A2<=A3...<=As 满足 A1+A2+A3+...+As= N,那么我们称序列 A1 A2……As 为N的一个拆分。现在要求N的拆分数目。( n<65) 例如:当N = 6 时 6 = 1 + 1 + 1 + 1 + 1 + 1 = 1 + 1 + 1 + 1 + 2 = 1 + 1 + 1 + 3 = 1 + 1 + 2 + 2 = 1 + 1 + 4 = 1 + 2 + 3 = 1 + 5 = 2 + 2 + 2 = 2 + 4 = 3 + 3    所以 6 的整数拆分个数为 10 。

Page 55: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

var a:array[1..100] of integer; total,n:integer; procedure output(s:integer); {s 为一个拆分的长度 } var i:integer; begin write(n,'=',a[1]:3); for i:=2 to s do write('+',a[i]:3); writeln; end;

Page 56: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

procedure dfs(d,low,rest:integer); var i:integer; begin if( ) then begin ( ) ; output(d-1); exit; end; if low>rest then ( ) ;{剪枝 } for i:=low to rest do { 枚举 a[d] 的所有可能值 } begin a[d]:=i; if a[d]<>n then dfs( ); end; end; begin read(n); dfs( ); writeln('Total=',total);end.

1,1,n

rest=0

inc(total)

exit

d+1,i,rest-i

low 为当前 a[d] 的下界 ,rest 为还剩下多少要拆分 .

Page 57: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

问题描述 :

将整数 n 分成 k 份,且每份不能为空,任意两种分法不能相同 ( 不考虑顺序 ) 。 例如: n=7 , k=3 ,下面三种分法被认为是相同的: 1 , 1 , 5 ; 1 , 5 , 1 ; 5 , 1 , 1 ;

问有多少种不同的分法。 输入: n , k ( 6<n<=200 , 2<=k<=6 )

输出:一个整数,即不同的分法。 样例 输入: 7 3

输出: 4

{ 四种分法为: 1 , 1 , 5 ;1 , 2 , 4 ;1 , 3 , 3 ;2 , 2 , 3;}

测试 3 、数的划分( 01年复赛)

Page 58: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

7=1+6

=2+5

=3+4

分析:按份数递增的顺序拆分,且被拆分的整数按递增顺序排列(避免重复)。

第一个数要小于等于 7 div 3

7=1+1+5

=1+2+4

=1+3+3 第二个数要小于等于 6 div 2

7=2+2+3 第二个数要小于等于 5 div 2

Page 59: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

var n,k,i:integer; tot:longint; a:array[1..6] of integer;procedure sum( kx:integer); var m , L:integer; begin if kx=k then begin tot:=tot+1; exit; end; L:=a[kx]; for m:=a[kx-1] to L div (k-kx+1) do begin a[kx]:=m; a[kx+1]:=L-m; sum(kx+1); end; end;

begin

read(n,k);

tot:=0;

for i:=1 to n div k do

begin

a[1]:=i;

a[2]:=n-i;

sum(2);

end;

writeln(tot);

end.

kx 为整数序号

已将 n拆成 k份

按递增要求枚举 n拆成两份的所有

可能方案

从第二个数出发递归拆分按递增要求穷

举 a[kx] 拆成两份的所有可能方案

Page 60: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

测试 4、选数 ( 01年复赛)

已知 n ( 1<=n<=20 )个整数 x1,x2,…,xn ( 1<=xi<=5000000 ),以及一个整数 k ( k<n )。从 n 个整数中任选 k 个整数相加,可分别得到一系列的和。 例如当 n=4 , k=3 , 4 个整数分别为 3,7,12,19 时,可得到的全部组合及它们的和为 3+7+12=22 , 3+7+19=29 , 7+12+19=38 , 3+12+19=34 。 现在,要求你计算出和为素数的组合共有多少种。如上例中,只有一种的和为素数: 3+7+19=29 。 输入: n , k x1,x2,…,xn 输出:一个整数 ( 满足条件的组合个数 ) 输入输出样例 输入: 4 3 3 7 12 19 输出: 1

Page 61: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

判断一个整数 P(P>1) 是否为素数最简单的方法就是看是否存在一个素数 a(a<=sqrt(P)) 是 P的约数,如果不存在,该数就为素数。

由于在此题中 1<=xi<=5000000,n<=20, 所以要判断的数 P不会超过 100000000 , sqrt(p)<=10000 ,因此,为了加快速度,我们可以先将 2…10000 之间的素数保存到一个数组里(共 1229个),这样速度估计将提高 5~6倍。

Page 62: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

var

n,k: integer;

ans,i: longint;

x: array[1 .. 20] of longint;

p: array[1 .. 1229] of integer;

function ss( i:longint ):boolean;

var

j:longint;

begin

j:=2;

while i mod j<>0 do j:=j+1;

if j>sqrt(i) then ss:=true

else ss:=false;

end;

procedure create; var s:integer; i:longint; begin s:=0; for i:=2 to 10000 do if ss ( i ) then begin s:=s+1; p[s]:=i; end; end;function fit( sum:longint ):boolean; var max,m:integer; begin max:=trunc( sqrt(sum) )+1; m:=1; while ( sum mod p[m]<>0 ) and ( p[m]<max ) do m:=m+1; if p[m]>=max then fit:=true else fit:=false; end;

判断 i 是否是素数

建立 10000 以内的素数集合

判断 k 个整数之和是否为素数

Page 63: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

procedure solve(left,now,all:longint); begin if ( left > n-now+1 ) then exit;{ 剪枝 } if ( left=0 ) then begin if fit(all) then ans:=ans+1; exit; end; solve( left , now+1 , all ); solve( left-1,now+1,all+x[now] ); end;begin create;{建立 10000 以内的素数表 } readln(n,k); for i:=1 to n do read(x[i]); ans:=0; solve( k,1,0 ); writeln(ans);end.

目前组合和为 all ,还需从第 x now..xn 中选出 left 个数。

如余下的数已不足 left 个,则回溯

当前组合不选 xno

w

当前组合选取 xno

w

Page 64: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

测试 5、合理排列 (04 年江苏)

问题描述: 由 m 个 A , n 个 B 组成若干个排列,从某个排列的位置 1 开始数,数到任意位置时都能保证 A 的个数不少于 B 的个数,则称该排列为合理排列。 如当 m=2,n=2 时,排列有 :AABB (合理) ABAB (合理) ABBA (不合理) BBAA (不合理),合理排列有 2 种。 又如当 m=3,n=2 时合理排列有 5 种: AAABB 、 AABAB 、 AABBA 、 ABAAB 、 ABABA输入输出: 输入只有一行两个整数 m,n ( 1<=n<=m<=12 )(用空格分隔) 输出一个整数表示所有的合理排列数。样例: 10.in 3 2 10.out 5

Page 65: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

var a,b,n,w:integer;{w 表示前 k 个字符中 'A' 的个数与 'B' 的个数的差 } s:longint;{合理排列数 }procedure try(k:integer);{k 表示目前确定第 K个字符 } begin if k=n+1 then begin s:=s+1; exit; end; if a>0 then begin {放‘ A’的情况 } a:=a-1; w:=w+1; try(k+1); w:=w-1; a:=a+1; end; if (w>0)and(b>0) then begin {放‘B’的情况 } b:=b-1; w:=w-1; try(k+1); w:=w+1; b:=b+1; end; end;begin readln(a,b); n:=a+b; {排列的长度 } a:=a-1; w:=1; {第 1 个字符必定为 'A'} s:=0; try(2); writeln(s);end.

Page 66: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

测试 6、构造字串

生成长度为 n 的字串,其字符从 26 个英文字母的前 p(p≤26) 个字母中选取,使得没有相邻的子序列相等。例如 p=3 , n=5 时

‘a b c b a’ 满足条件

‘a b c b c’ 不满足条件

输入: n , p

输出:所有满足条件的字串

Page 67: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

copy(s,length(s)-i+1,i)<>copy(s,length(s)-2*i+1,i)

1<= i<=at div 2

s=‘abcba ‘

i=1 时,a<>b

i=2 时,ba<>bc

s=‘abcbc ‘

i=1 时,c<>b

i=2 时,bc=bc

Page 68: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

var n,p,L:integer; t:longint; ed:char;{ 可选字母集中的最大字母 } s:string;{ 满足条件的字串 }procedure solve(at:integer); var ch:char; i:integer; begin if at=n+1 then begin writeln(s); inc(t); exit end; for ch:='a' to ed do begin s:=s+ch; L:=length(s); i:=1;{i 是子串长度 } while(i<=at div 2)and(copy(s,L-i+1,i)<>copy(s,L-2*i+1,i)) do inc(i); if i>at div 2 then solve(at+1); delete(s,length(s),1); end; end;

搜索范围: 'a'.. chr(ord('a')+p-1)

约束条件:当前字串没有相邻子串相等的情况

递归扩展下一个字母

begin{ 主程序 } readln(n,p); ed:=chr( ord('a') + p - 1); s:=''; tl:=0; solve(1); writeln('Total=',tl);end.待扩展的字母序号 at

Page 69: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

【问题描述】

棋盘上 A 点有一个过河卒,需要走到目标 B 点。卒行走的规则:可以向下、或者向右。同时在棋盘上 C 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。

棋盘用坐标表示, A 点 (0, 0) 、 B 点 (n, m)(n, m 为不超过 20 的整数 ) ,同样马的位置坐标是需要给出的。现在要求你计算出卒从 A 点能够到达 B点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。

【输入】

一行四个数据,分别表示 B 点坐标和马的坐标。

【输出】

一个数据,表示所有的路径条数。

【样例】

输入 :6 6 3 3

输出 :6

测试 7、马拦过河卒( 02年复赛)

Page 70: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

const dx:array[1..8] of integer=(-2,-1,1,2,2,1,-1,-2); dy:array[1..8] of integer=(1,2,2,1,-1,-2,-2,-1);var n,m,x,y,i,j: byte; g:array[0..20,0..20] of 0..1; c:longint;procedure sol(x,y:integer); var i:integer; begin if (x=n) and (y=m) then begin c:=c+1; exit; end; if (y<m) and (g[x,y+1]=0) then sol(x,y+1); if (x<n ) and (g[x+1,y]=0) then sol(x+1,y); end;begin readln(n,m,x,y); g[x,y] := 1; for i:=1 to 8 do if (x+dx[i]>=0) and (x+dx[i]<=n) and (y+dy[i]>=0) and (y+dy[i]<=m) then g[x+dx[i],y+dy[i]]:=1; sol(0,0); writeln(c);end.

x,y 为当前马所在的位置

目标 回溯

向右走

向下走

计算马的控制点

Page 71: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

测试 8、 01 字符串问题

问题描述:

输出仅由 0和 1组成的长度为 n的字符串,并且其中不可含有三个连续的相同字串。

输入:字符串长度 n( n<=40)

输出:所有满足条件的字符串的个数。

样例输入 :

2

样例输出 :

4

Page 72: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

var n : integer; lev : integer; {位指针} tot : longint; list : array[1..50] of integer; { 01序列}function right(lev:integer):boolean; {有没有三个连续的相同子串} var p,q,r,x,y,z : integer; begin x:=lev; y:=lev-1; z:=lev-2; while z>0 do begin p:=x;q:=y;r:=z; while (r<y) and (list[p]=list[q]) and (list[q]=list[r]) do begin inc(p) ; inc(q) ; inc(r); end; if r=y then begin right:=false ; exit; end; x:=x-1;y:=y-2;z:=z-3 end; right:=true; end;

0 1 0 0 1 0 0 1 0

Z Y X

Z Y X

Z Y X

R Q PR Q PR Q PR Q P

Page 73: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

procedure search(lev:integer); begin if lev>n then begin tot:=tot+2; exit; end; list[lev]:=0; {先填 0} if right(lev) then search(lev+1); list[lev]:=1; {后填 1} if right(lev) then search(lev+1); end;begin readln(n); list[1]:=0; {第一位为 0 } lev:=1; tot:=0; search(2); writeln(tot);end.

序列的对称性:011001

100110

Page 74: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

测试 9.错排问题 (02年初赛 )

在书架上放有编号为 1 , 2 ,…, n 的 n 本书。现将 n 本书全部取下然后再放回去,当放回去时要求每本书都不能放在原来的位置上。 例如: n = 3 时: 原来位置为: 1 2 3 放回去时只能为: 3 1 2 或 2 3 1 这两种 问题:求当 n = 5时满足以上条件的放法共有多少种?

Page 75: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

var a:array[1..50] of integer; s:set of 1..50; n,num:integer;procedure print; var i:integer; begin inc(num); write('No.',num,' '); for i:=1 to n do write(a[i]:4); writeln; end;procedure cuopai(k:integer); var i:integer; begin if ( )then begin print; exit; end;

for i:=1 to n do if not(i in s)and ( ) then begin a[k]:=i; s:=s+[i]; ( ) ; a[k]:=0; ( ); end; end;begin readln(n); ( ); num:=0; cuopai(1); writeln('Total=',num);end.

s:=[]

i<>k

cuopai(k+1)

k=n+1

s:=s-[i]

Page 76: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

测试 10.

  公园门票每张 5角,如果有 2n 个人排队购票,每人一张,并且其中一半人恰有5角钱,另一半人恰有1元钱,而票房无零钱可找,那么有多少种方法将这 2n 个人排成一列,顺次购票,使得不至于因票房无零钱可找而耽误时间?

2n 个 0与 1 组成的数,从最左边算起,任何位置 0的个数不得小于 1 的个数,这样的排列有多少种?

Page 77: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

const maxn=10;var a:array[1..maxn*2] of integer; n,num:integer;procedure try(k,n0,n1:integer); var i,j:integer; begin if( )then begin inc(num); write('No.',num,' '); for i:=1 to 2*n do write(a[i]:2); writeln; ( ); end; if(n0=n1)and(n0<n)then begin a[k]:=0; try( ); end;

   if( )and(n0<n)then   begin   for i:=0 to 1 do  begin   a[k]:=i;   if i=0 then try(k+1,n0+1,n1)   else try(k+1,n0,n1+1);  end;   end;   if ( )then    begin a[k]:=1;      try(k+1,n0,n1+1); end; end;begin fillchar(a,sizeof(a),0);  readln(n);  num:=0;   try(1,0,0);end.

k=2*n+1

k+1,n0+1,n1

n0>n1

n0=n

exit

n0 是有5角钱的人数 ,n1 是有1元钱的人数

Page 78: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

测试 11 、棋盘覆盖

有边长为 N(偶数)的正方形,用 N*N/2 个长为 2 、宽为 1 的长方形将它全部覆盖,请找出所有覆盖方法。如 N=4 时的一种覆盖方法及输出格式如下所示。

1 2 2 4

1 3 3 4

5 6 6 8

5 7 7 8

输出:(数字为长方形编号)1 2 2 41 3 3 45 6 6 85 7 7 8

Page 79: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

var n:integer; t:longint; a:array[1..10,1..10] of integer;procedure print; var i,j:integer; begin ( ) ; for i:=1 to n do begin for j:=1 to n do write(a[i,j]:5); writeln; end; end;procedure try(i:integer); var j,k:integer; begin j:=0; repeat{找到第一个未覆盖的空格 (j,k)} j:=j+1; k:=1; while(k<=n)and( ) do inc(k); until k<=n; a[j,k]:=i;

if (j<n)and(a[j+1,k]=0)then begin a[j+1,k]:=i; if i*2<n*n then try(i+1) else print; ( ) ; end; if (k<n)and(a[j,k+1]=0)then begin a[j,k+1]:=i; if i*2<n*n then try(i+1) else print; ( ) ; end; ( ) ; end;begin readln(n); try(1); write(t);end.a[j,k]>0

a[j+1,k]:=0

a[j,k+1]:=0

a[j,k]:=0

inc(t)

Page 80: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

测试 12、组合的输出

【问题描述】 排列与组合是常用的数学方法,其中组合就是从 n个元素中抽出 r个元素 ( 不分顺序且 r< = n) ,我们可以简单地将n个元素理解为自然数 1,2,…, n,从中任取 r个数。 现要求你输出所有的组合。 例如 n= 5, r = 3,所有组合为: 1 2 3 1 2 4 1 2 5 1 3 4 1 3 5 1 4 5 2 3 4 2 3 5 2 4 5 3 4 5【输入】 一行两个自然数 n、 r(1<n<21, 1< = r< = n) 。【输出】 所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,每个元素占三个字符的位置,所有的组合也按字典顺序。

Page 81: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

const max=20;var a:array[0..max]of integer; n,r:1..max;procedure compages (k:integer); var i,j:integer; begin for i:=( ) do begin a[k]:=i; if( ) then begin for j:=1 to r do write(a[j]:3); writeln; end else( ) ; end; end;begin readln(n,r); compages(1); {从第一个元素开始选取组合数 }end.

a[k-1]+1 to n-(r-k)

k=r

compages(k+1)

当前所选元素最小为前一个元素加 1 ,最大为 n-(r-k) ,因为后面还有 (r-k) 个元素要选取,至少为每次选取留一个 .

Page 82: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

例 1、翻硬币 (03年初赛题 )

题目描述:   一摞硬币共有 m枚,每一枚都是正面朝上。取下最上面的一枚硬币,将它翻面后放回原处。然后取下最上面的 2枚硬币,将他们一起翻面后再放回原处。再取 3枚,取 4枚……直至 m枚。然后再从这摞硬币最上面的一枚开始,重复刚才的做法。这样一直做下去,直到这摞硬币中的每一枚又都是正面朝上为止。   输 入:   仅有的一个数字是这摞硬币的枚数 m , 0<m<50 。   输 出:   为了使这摞硬币中的每一枚又都是正面朝上所必需翻的次数。   输入样例:      30   输出样例:      899

Page 83: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

const max=100; var a,b:array[1..max] of boolean; m,n:integer;procedure print; var i:integer;  p:boolean; begin (     ); p:=true; for i:=1 to m do if a[i] then p:=false; if p then begin  writeln('total=',n);  (    ) ; end; end;

procedure turn(k:integer); var i:integer; begin if k>m then (    ) ; b:=a; for i:=1 to k do   b[i]:=not(      ); a:=b; print; (      ); end;begin readln(m); fillchar(a,sizeof(a),false); n:=0;{翻面次数} turn(1);{翻一个硬币}end.

k:=1

halt

a[k+1-i]

turn(k+1)inc(n)

Page 84: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

例 2、求全排列 (06年初赛题 )

下面程序的功能是利用递归方法生成从 1 到 n(n<10)的 n个数的全部可能的排列(不一定按升序输出)。例如,输入 3,则应该输出(每行输出 5 个排列):123 132 213 231 321 312

Page 85: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

var i,n,k:integer; a:array[1..10] of integer; count:longint; procedure perm(k:integer); var j,p,t:integer; begin if( )then begin ( ); for p:=1 to k do write(a[p]:1); write(' '); if( )then writeln; exit; end;

for j:=k to n do begin t:=a[k]; a[k]:=a[j]; a[j]:=t; perm(k+1) ; t:=a[k];( ) end end; begin writeln('Entry n:'); read(n); count:=0; for i:=1 to n do a[i]:=i; ( ) end.

perm(1)

K=n

inc(count)

count mod 5=0

a[k]:=a[j]; a[j]:=t ;

Page 86: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

123

123

123 132

123 132

213

213

213

231

231

321

321

321

312

312

Page 87: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

例 3、 2 的幂次方表示 (98年复赛 )

任何一个正整数都可以用 2 的幂次方表示。例如: 137=27+23+20

同时约定次方用括号来表示,即 ab 可表示为 a (b) 。由此可知, 137 可表示为: 2 (7)+2 (3)+2 (0)进一步: 7=22+2+20 (21 用 2 表示 ) 3=2+20

所以最后 137 可表示为: 2(2(2)+2+2(0))+2(2+2(0))+2(0)又如: 1315=210+28+25+2+20

所以 1315 最后可表示为:2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)输入:一个正整数 (n<=20000)输出:符合约定的 n 的 0,2 表示 ( 在表示中不能有空格 )

Page 88: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

var n:longint;procedure try(n:longint); var a:array[1..1000]of integer; k,p,i,t:longint; begin fillchar(a,sizeof(a),0); k:=n; p:=0; t:=0; while( )do begin inc(p); a[p]:=k mod 2; if (a[p]<>0)and(t=0) then t:=p; {t 记录二进制数中从最低位开始第一个 '1' 的位置 }

k:=k div 2; end; for i:=p downto t+1 do if a[i]=1 then if( )then write('2+') else begin write('2(');( ); write(')+'); end; {情况一 }

if( )then write('2(0)') else begin if t=2 then write('2') else begin write('2('); ( ); write(')'); end; end; {情况二 } end;begin readln(n); try(n);end.

由于最后一项后面没有加号,其它项之后有加号,因此程序中进行了处别对待.

k>0

i=2

try(i-1)

t=1

try(t-1)

Page 89: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

例 4、集合找数

【问题描述】 集合 A 中的元素有以下特征: (1) 数 1 是 A 中的元素; (2) 如果 X 是 A 中的元素,则 2X+1 , 3X+1 也是 A 中的元素; (3) 除了条件( 1 ),( 2 )以外的所有元素均不是 A中的元素; 给定数 N ,请求出集合中的第 N 个元素。 例如 N=500 , A500=3351 N=10000 , A10000=157653

Page 90: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

算法分析: 从 2 开始,对其后的每一个数判断是否符合条件,如果符合则记数器加 1 ,若不符合条件则试下一个数,直到产生了 N个数为止。但在判断是否符合条件时,我们引入递归的思想去判断:例如,要判断 15 是否符合条件,先解出 2X+1=15 或 3X+1=15 的 X 值( X 必须取自然数),可见, X=7 ,然后任务转化为判断 7 是否符合条件,同理,求出 2X+1=7 或 3X+1=7 的X 值, X=2 或 X=3 ;然后继续判断 2 和 3 是否符合条件,求出2X+1=2 或 3X+1=2 或 2X+1=3 或 3X+1=2 的 X 值,有 2X+1=3得 X=1 ;最终能得到 1 ,符合数列的定义:“数 1 是 A 中的元素”,说明 15 是集合中的元素。如果最终无法得出 1 ,则说明不是集合的元素。

Page 91: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

var n,Long,x:longint;function init(x:longint):boolean; begin if x=1 then init:=true else if( (x-1)mod 2=0)and( init((x-1)div 2)) or then init:=true else       ; end;begin readln(n); x:=0; Long:=0; while Long<n do begin x:=      ; if init(x) then ; end; writeln(  );end.

x+1Long:=Long+1

x

init:=false((x-1)mod 3=0)and(init((x-1)div 3))

Page 92: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

给出一棵二叉树的中序与后序排列。求出它的先序排列。(约定树结点用不同的大写字母表示,长度≤ 8)。

[ 样例 ]输入: BADC BDCA输出: ABCD

分析 : 通过对比二叉树的中序与后序排列,我们可以找出根节点及左右子树。同样的,也可以通过对比左子树的中序与

……后序排列,找出左子树的根节点 可见,该问题能够被递归描述。当找到最后一个根节点时,递归无法再进行下去,这就是递归结束的边界条件。

例 5 、求先序遍历

Page 93: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

var z,h:string; procedure find(a,b:string); var s,L : integer; begin L:=length(b); if( )then write(b){是叶子结点 } else begin write(b[L]);{后序遍历的最后一个是根 } s:=pos(b[L],a);{在中序遍历中找出根的位置 } if s-1>0 then {若存在左子树 } find(copy( ),copy( )); {递归左子树 } if L-s>0 then {若存在右子树 } find(copy( ),copy( )); {递归右子树 } end; end;begin readln(z); readln(h); find(z,h);end.

L=1

a,1,s-1 b,1,s-1

a,s+1,L-s b,s,L-s

DEABFCHGDEAFHGCB

BAEDCFGH

Page 94: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

例 6、黑白棋子的移动

问题描述 : 有 2n 个棋子( n≥4 )排成一行,开始位置白子全部在左边,黑子全部在右边,如下图为 n=5 时的情况:

○○○○○●●●●● ——

移动棋子的规则是:每次必须同时移动相邻的两个棋子,颜色不限,可以左移也可以右移到空位上去,但不能调换两个棋子的左右位置。每次移动必须跳过若干个棋子(不能平移),要求最后能移成黑白相间的一行棋子。如 n=5 时,成为:

—— ○●○●○●○●○● 任务:编程打印出移动过程。

【样例】chessman.in chessman.out7 step 0:ooooooo*******--

step 1:oooooo--******o* step 2:oooooo******--o* step 3:ooooo--*****o*o* step 4:ooooo*****--o*o* step 5:oooo--****o*o*o* step 6:oooo****--o*o*o* step 7:ooo--***o*o*o*o* step 8:ooo*o**--*o*o*o* step 9:o--*o**oo*o*o*o* step10:o*o*o*--o*o*o*o* step11:--o*o*o*o*o*o*o*

Page 95: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

我们先从 n=4 开始试试看,初始时: ○○○○●●●● ——第 1 步:○○○——●●●○●第 2 步:○○○●○●●——●第 3 步:○——●○●●○○●第 4 步:○●○●○●——○●第 5 步:——○●○●○●○●如果 n=5 呢?我们继续尝试,希望看出一些规律,初始时: ○○○○○●●●●● ——第 1 步:○○○○——●●●●○●第 2 步:○○○○●●●●——○● 这样, n=5 的问题又分解成了 n=4 的情况,下面只要再做一下 n=4 的 5 个步骤就行了。同理, n=6 的情况又可以分解成 n=5 的情况,……,所以,对于一个规模为 n 的问题,我们很容易地就把它转化为规模为 n-1 的相同类型子问题。 数据结构如下:数组 c[1..max] 用来作为棋子移动的场所,初始时, c[1]~c[n] 存放白子(用字符 o 表示), c[n+1]~c[2n] 存放黑子(用字符 * 表示), c[2n+1] , c[2n+2] 为空位置(用字符—表示)。最后结果在 c[3]~c[2n+2] 中。

Page 96: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

const max=100;var n,st,sp:integer; c:array[1..max] of char;procedure print; {打印 } var i:integer; begin write('step',st:2,':'); for i:=1 to 2*n+2 do write(c[i]); writeln; ( ) end;procedure init(n:integer); { 初始化 } var i:integer; begin st:=0; { 移动步数 } sp:=2*n+1; for i:=1 to n do c[i]:='o'; for i:=n+1 to 2*n do c[i]:='*'; c[2*n+1]:='-';c[2*n+2]:='-'; print end;

procedure move(k:integer); { 移动一步 } var j:integer; begin for j:=0 to 1 do { 移两个棋 } begin c[sp+j]:=c[k+j] ; c[k+j]:='-'; end; ( ); print end;procedure mv(n:integer); var i,k:integer; begin if n=4 then begin move(4); move(8); move(2); move(7);( ) end else begin move(n); move(2*n-1); ( ) end end;begin readln(n); init(n); mv(n);end.

move(1)

mv(n-1)

sp:=k

st:=st+1

第 1 步:○○○——●●●○●第 2 步:○○○●○●●——●第 3 步:○——●○●●○○●第 4 步:○●○●○●——○●第 5 步:——○●○●○●○●

第 1 步:○○○○——●●●●○●

第 2 步:○○○○●●●●——○●

Page 97: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

数独的规则很简单, 3x3 的数独题就是一个 9x9 的方阵,方阵中的每一个格子可以填入 1-9 的数字,要求是:每行不能有重复的数字,每列也不能有重复的数字,而且 9x9 的方阵再划分为 9 个 3x3 的小方阵,要求每个小方阵中也不能有重复的数字。按照正常的数独规则,每个问题必须只能有一个答案,不过这个规则与我们的搜索算法无关,我们可以不理睬问题是否无解或有多个解,只管搜索就是了

Page 98: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

var g,a,b,c:array[0..8,0..8] of longint; v:array[0..26,0..9]of boolean; i,j,x,y,k,n:longint;procedure print;beginfor i:=0 to 8 do begin for j:=0 to 8 do writeln(g[i,j],' '); writeln; end;close(output);halt;end;procedure search(i,j,dep:longint);var k:longint;beginif ① then print;if j>8 then ② else if g[i,j]>0 then ③ else for k:=1 to 9 do if ④ then begin ⑤ ; v[a[i,j],k]:=false; v[b[i,j],k]:=false; v[c[i,j],k]:=false; search(i,j+1,dep+1); v[a[i,j],k]:=true; v[b[i,j],k]:=true; v[c[i,j],k]:=true; g[i,j]:=0; end;end;

beginfillchar(v,sizeof(v),true);for i:=0 to 8 do for j:=0 to 8 do begin read(g[i,j]); if g[i,j]=0 then inc(n); a[i,j]:=i div 3*3+j div 3; b[i,j]:=9+i; { 按区的第二排 } c[i,j]:=18+j; { 按区的第三排 } v[a[i,j],g[i,j]]:=false; v[b[i,j],g[i,j]]:=false; v[c[i,j],g[i,j]]:=false; end;search(0,0,1);end.

①dep>n; ②search(i+1,0,dep); ③search(i,j+1,dep); ④v[a[i,j],k] and v[b[i,j],k] and v[c[i,j],k]; ⑤g[i,j]:=k;

Page 99: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

const    size=100;var   n,m,x,y,i :integer;   r: array[1.. size] of integer;   map : array[1..size, 1..size] of boolean;   found : boolean;function successful : boolean;var   i : integer;begin   for i :=1 to n do      if not map[r[i]][r[i mod n + 1]]      then begin     successful := false;       exit;      end;   successful :=true;end;procedure swap(var a, b : integer);var    t : integer;begin    t := a;    a := b;    b := t;end;procedure perm(left, right : integer);var i : integer;begin if found then exit; if left > right then begin if successful then begin for i := 1 to n do writeln(r[i], ' '); found := true; end; exit; end; for i:= left to right do begin swap(r[left], r[i]); perm(left + 1, right); swap(r[left], r[i]); end;end;

begin    readln(n, m);    fillchar(map, sizeof(map), false);    for i := 1 to m do    begin  readln(x, y);   map[x][y] := true;   map[y][x] := true;    end;    for i := 1 to n do      r[i] := i;    found := false;    perm(1, n);    if not found    then   writeln('No soloution');end.输入 :9 121 22 33 44 55 66 11 72 73 84 85 96 9

1 6 9 5 4 8 3 2 7

1

2

34

5

6

7

8

9

Page 100: program  p4 (input , output) ; procedure rever ; var     c : char ; begin     read(c) ;

const    size=100;var   n,m,x,y,i :integer;   r: array[1.. size] of integer;   map : array[1..size, 1..size] of boolean;   found : boolean;function successful : boolean;var   i : integer;begin   for i :=1 to n do      if not map[r[i]][r[i mod n + 1]]      then begin     successful := false;       exit;      end;   successful :=true;end;procedure swap(var a, b : integer);var    t : integer;begin    t := a;    a := b;    b := t;end;procedure perm(left, right : integer);var i : integer;begin if found then exit; if left > right then begin if successful then begin for i := 1 to n do writeln(r[i], ' '); found := true; end; exit; end; for i:= left to right do begin swap(r[left], r[i]); perm(left + 1, right); swap(r[left], r[i]); end;end;

begin    readln(n, m);    fillchar(map, sizeof(map), false);    for i := 1 to m do    begin  readln(x, y);   map[x][y] := true;   map[y][x] := true;    end;    for i := 1 to n do      r[i] := i;    found := false;    perm(1, n);    if not found    then   writeln('No soloution');end.输入 :

1 2 3 4 5 6 7 8 9

left

right

1 2 3 4 5 6 7 8 9

2 1 3 4 5 6 7 8 9

left

right

1 3 2 4 5 6 7 8 9

1 3 4 2 5 6 7 8 9