pascal教程8动态规划.docx

上传人:b****6 文档编号:7613518 上传时间:2023-01-25 格式:DOCX 页数:18 大小:43.69KB
下载 相关 举报
pascal教程8动态规划.docx_第1页
第1页 / 共18页
pascal教程8动态规划.docx_第2页
第2页 / 共18页
pascal教程8动态规划.docx_第3页
第3页 / 共18页
pascal教程8动态规划.docx_第4页
第4页 / 共18页
pascal教程8动态规划.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

pascal教程8动态规划.docx

《pascal教程8动态规划.docx》由会员分享,可在线阅读,更多相关《pascal教程8动态规划.docx(18页珍藏版)》请在冰豆网上搜索。

pascal教程8动态规划.docx

pascal教程8动态规划

第八章动态规划

8.1字串距离

源程序名blast.*(pas,c,cpp)

可执行文件名blast.exe

输入文件名blast.in

输出文件名blast.out

【问题描述】

设有字符串X,我们称在X的头尾及中间插入任意多个空格后构成的新字符串为X的扩展串,如字符串X为”abcbcd”,则字符串“abcb□cd”,“□a□bcbcd□”和“abcb□cd□”都是X的扩展串,这里“□”代表空格字符。

如果A1是字符串A的扩展串,B1是字符串B的扩展串,A1与B1具有相同的长度,那么我扪定义字符串A1与B1的距离为相应位置上的字符的距离总和,而两个非空格字符的距离定义为它们的ASCII码的差的绝对值,而空格字符与其他任意字符之间的距离为已知的定值K,空格字符与空格字符的距离为0。

在字符串A、B的所有扩展串中,必定存在两个等长的扩展串A1、B1,使得A1与B1之间的距离达到最小,我们将这一距离定义为字符串A、B的距离。

请你写一个程序,求出字符串A、B的距离。

【输入】

输入文件第一行为字符串A,第二行为字符串B。

A、B均由小写字母组成且长度均不超过2000。

第三行为一个整数K(1≤K≤100),表示空格与其他字符的距离。

【输出】

输出文件仅一行包含一个整数,表示所求得字符串A、B的距离。

【样例】

blast.inblast.out

cmc10

snmn

2

【算法分析】

字符串A和B的扩展串最大长度是A和B的长度之和。

如字符串A为“abcbd”,字符串B为“bbcd”,它们的长度分别是la=5、lb=4,则它们的扩展串长度最大值为LA+LB=9,即A的扩展串的5个字符分别对应B的扩展串中的5个空格,相应B的扩展串的4个字符对应A的扩展串中的4个空格。

例如下面是两个字符串的长度为9的扩展串:

a□bc□b□d□

□b□□b□c□d

而A和B的最短扩展串长度为la与lb的较大者,下面是A和B的长度最短的扩展串:

abcbd

b□bcd

因此,两个字符串的等长扩展串的数量是非常大的,寻找最佳“匹配”(对应位置字符距离和最小)的任务十分繁重,用穷举法无法忍受,何况本题字符串长度达到2000,巨大的数据规模,势必启发我们必须寻求更有效的方法:

动态规划。

为A串中A1到Ai的一个扩展串,为B串中B1到Bj的一个扩展串。

这两个扩展串形成最佳匹配的条件是

(1)长度一样;

(2)对应位置字符距离之和最小。

首先分析扩展串与扩展串长度一样的构造方法。

扩展串与扩展串可以从下列三种情况扩张成等长:

(1)为两个等长的扩展串,则在后加一空格,加字符Bj;

(2)为两个等长的扩展串,则在添加字符Ai,在后加一空格;

(3)为两个等长的扩展串,则在后添加字符Ai,在后添加字符Bj。

其次,如何使扩展成等长的这两个扩展串为最佳匹配,即对应位置字符距离之和最小,其前提是上述三种扩展方法中,被扩展的三对等长的扩展串都应该是最佳匹配,以这三种扩展方法形成的等长扩展串(A1,A2,…,Ai>和也有三种不同情形,其中对应位置字符距离之和最小的是最佳匹配。

为了能量化上述的构造过程,引入记号g[i,j]为字符串A的子串A1,A2,…,Ai与字符串B的子串B1,B2,…,Bj的距离,也就是扩展串与扩展串是一个最佳匹配。

则有下列状态转移方程:

g[i,j]=Min{g[i-1,j]+k,g[i,j-1]+k,g[i-1,j-1]+

}0≤i≤La0≤j≤Lb

其中,k位字符与字符之间的距离;

为字符ai与字符bi的距离。

初始值:

g[0,0]=0g[0,j]=j·kg[i,0]=i·k

综上所述,本题的主要算法如下:

(1)数据结构

vara,b:

array[1..2000]ofbyte;{以ASCII码表示的字符串}

g:

array[0..2000,0..2000]oflongint;{各阶段的匹配距离}

(2)读入字符串A、B,转换为ASCII码

la:

=0;lb:

=0;

whilenot(eoln(f))do{子串长度单元}

begin{从文件中读入一行字符}

read(f,c);

inc(la);

a[la]:

=ord(c);

end;

readln(f);

whilenot(eoln(f))do

begin

read(f,c);

inc(lb);

b[lb]:

=ord(c);

end;

readln(f);

(3)根据状态转移方程求g[la,lb]

g[0,0]:

=0;

fori:

=1toladog[i,0]:

=k+g[i-1,0];

forj:

=1tolbdog[0,j]:

=k+g[0,j-1];

fori:

=1tolado

forj:

=1tolbdo

begin

g[i,j]:

=k+g[i-1,j];

temp:

=g[i,j-1]+k;

ifg[i,j]>temptheng[i,j]:

=temp;

temp:

=g[i-1,j-1]+abs(a[i]-b[j]);

ifg[i,j]>temptheng[i,j]:

=temp;

end;

(4)输出

writeln(f,g[la,lb]);

8.2血缘关系

源程序名family.?

?

?

(pas,c,cpp)

可执行文件名family.exe

输入文件名family.in

输出文件名family.out

【问题描述】

我们正在研究妖怪家族的血缘关系。

每个妖怪都有相同数量的基因,但是不同的妖怪的基因可能是不同的。

我们希望知道任意给定的两个妖怪之间究竟有多少相同的基因。

由于基因数量相当庞大,直接检测是行不通的。

但是,我们知道妖怪家族的家谱,所以我们可以根据家谱来估算两个妖怪之间相同基因的数量。

妖怪之间的基因继承关系相当简单:

如果妖怪C是妖怪A和B的孩子,则C的任意一个基因只能是继承A或B的基因,继承A或B的概率各占50%。

所有基因可认为是相互独立的,每个基因的继承关系不受别的基因影响。

现在,我们来定义两个妖怪X和Y的基因相似程度。

例如,有一个家族,这个家族中有两个毫无关系(没有相同基因)的妖怪A和B,及它们的孩子C和D。

那么C和D相似程度是多少呢?

因为C和D的基因都来自A和B,从概率来说,各占50%。

所以,依概率计算C和D平均有50%的相同基因,C和D的基因相似程度为50%。

需要注意的是,如果A和B之间存在相同基因的话,C和D的基因相似程度就不再是50%了。

你的任务是写一个程序,对于给定的家谱以及成对出现的妖怪,计算它们之间的基因相似程度。

【输入】

第一行两个整数n和k。

n(2≤n≤300)表示家族中成员数,它们分别用1,2,…,n来表示。

k(0≤k≤n-2)表示这个家族中有父母的妖怪数量(其他的妖怪没有父母,它们之间可以认为毫无关系,即没有任何相同基因)。

接下来的k行,每行三个整数a,b,c,表示妖怪a是妖怪b的孩子。

然后是一行一个整数m(1≤m≤n2),表示需要计算基因相似程度的妖怪对数。

接下来的m行,每行两个整数,表示需要计算基因相似程度的两个妖怪。

你可以认为这里给出的家谱总是合法的。

具体来说就是,没有任何的妖怪会成为自己的祖先,并且你也不必担心会存在性别错乱问题。

【输出】

共m行。

可k行表示第k对妖怪之间的基因相似程度。

你必须按百分比输出,有多少精度就输出多少,但不允许出现多余的0(注意,0.001的情况应输出0.1%,而不是.1%)。

具体格式参见样例。

【样例】

family.infamily.out

740%

41250%

52381.25%

645100%

756

4

12

26

75

33

【知识准备】

(1)基本的概率计算知识;

(2)递推原理(包括记忆化搜索)。

【算法分析】

本题是一道概率计算题,但这个概率计算又是建立在FamilyTree模型上的。

FamilyTree是一个有向无环图,有明显的阶段性(辈分关系),而且没有后效性(没有人可以成为自己的祖先),符合动态规划模型的基本条件。

因此,应该可以套用类似动态规划的方法来解决。

我们先来明确一下相似程度的计算方法。

假设我们要求的是A和B的相似程度(设为P(A,B)),那么有两种情况是显然的:

(1)A=B:

P(A,B)=1;

(2)A与B无相同基因:

P(A,B)=0。

这是计算其他复杂情况的基础。

因为动态规划就是从一些特定的状态(边界条件)出发,分阶段推出其他状态的值的。

再来看一般的情况,设A0和A1是A的父母。

那么,取概率平均情况,A拥有A0和A1的基因各占一半。

假设A0与B的相似程度为P(A0,B),A1与B的相似程度为P(A1,B),那么P(A,B)与P(A0,B)和P(A1,B)之间应该是一个什么样的关系呢?

很容易猜想到:

P(A,B)=(P(A0,B)+P(A1,B))/2

但是,这只是一个猜想。

要让它变为一个结论还需要证明:

我们用归纳法来证明。

首先,我们知道,在这个问题中不同基因都是从特定的祖先传下来的,不会出现同一个基因采自不同的祖先的情况(注:

这里的祖先是指那些没有父母的妖怪)。

所以,如果A与B有相同的基因,这些基因必然来自同一个祖先。

这里,我们最想说明的是,祖先那代是不存在两个人,它们之间不同的基因相同的概率不一样,因为它们相同的概率都是0。

现在,A有祖先A0和A1,A0和A1与B的相似程度分别为P(A0,B)和P(A1,B)。

从祖先一代开始归纳,可由归纳假设A0与B、A1与B之间每个基因相同的概率都是一样的,分别都是P(A0,B)和P(A1,B)。

A的单个基因,它可能是继承A0的,也可能是继承A1的,概率各50%。

继承A0的话与B相同的概率是P(A0,B),继承A1的话与B相同的概率是P(A1,B)。

那么这个基因与B相同的概率就是:

(P(A0,B)+P(A1,B))/2

因此,A的每个基因与B相同的概率都是(P(A0,B)+P(A1,B))/2,具有相同的概率。

进而,A与B相同基因的数量概率平均也为(P(A0,B)+P(A1,B))/2,A与B的相似程度P(A,B)=(P(A0,B)+P(A1,B))/2

这样就归纳证明了P(A,B)的概率递推公式。

下面总结一下前面得出的结论:

(1)边界条件:

①A=B:

P(A,B)=1;

②A与B无相同基因:

P(A,B)=0;

(2)递推关系:

P(A,B)=(P(A0,B)+P(A1,B))/2,其中A0和A1是A的父母

有了边界条件和递推关系,以及FamilyTree的阶段性和无后效性作为前提,用动态规划解决问题的所有条件都已满足。

应该说,从理论上来讲,本题已经完全解决。

下面需要讨论的仅仅是实现方法而已。

动态规划的实现方法有两种:

一种是逆向的递推,另一种是正向的记忆化搜索(递归)。

这两种方法都是可行的,区别仅仅在于,递推需要更多的考虑状态的阶段性,按照阶段计算出所有状态的值;而记忆化搜索只需要承认状态具有阶段性,无需考虑阶段,只需要按照递推式本身设计带记忆化的递归函数即可。

本题给出的仅仅是一棵FamilyTree,并没有给出FamilyTree的阶段,如果要用递推的话,就必须先给FamilyTree分阶段(拓扑排序)。

所以,本题显然更适合用记忆化搜索来实现。

另外,需要注意的是,本题所求的答案是有多少精度就输出多少精度。

300个节点的图,少说也可以构成几十层的FamilyTree,算出的结果至少也有小数点后几十位。

所以,高精度是必不可少的(保险起见,可以设置300位的高精度)。

分析一下本题的时间复杂度。

动态规划的状态有n2个,转移代价为O(C)(高精度计算的代价)。

因此,时间复杂度为为O(Cn2),n≤300。

严格的讲,本题的算法不能算动态规划的方法,因为本题不存在最优化,应该仅仅是一个递推。

不过,从分析问题的过程来看,它里面包含了很多动态规划的思想,例如,阶段性和无后效性,特别是使用了动态规划的一种实现方法记忆化搜索。

8.3尼克的任务

源程序名lignja.?

?

?

(pas,c,cpp)

可执行文件名lignja.exe

输入文件名lignja.in

输出文件名lignja.out

【问题描述】

尼克每天上班之前都连接上英特网,接收他的上司发来的邮件,这些邮件包含了尼克主管的部门当天要完成的全部任务,每个任务由一个开始时刻与一个持续时间构成。

尼克的一个工作日为N分钟,从第一分钟开始到第N分钟结束。

当尼克到达单位后他就开始干活。

如果在同一时刻有多个任务需要完戍,尼克可以任选其中的一个来做,而其余的则由他的同事完成,反之如果只有一个任务,则该任务必需由尼克去完成,假如某些任务开始时刻尼克正在工作,则这些任务也由尼克的同事完成。

如果某任务于第P分钟开始,持续时间为T分钟,则该任务将在第P+T-1分钟结束。

写一个程序计算尼克应该如何选取任务,才能获得最大的空暇时间。

【输入】

输入数据第一行含两个用空格隔开的整数N和K(1≤N≤10000,1≤K≤10000),N表示尼克的工作时间,单位为分钟,K表示任务总数。

接下来共有K行,每一行有两个用空格隔开的整数P和T,表示该任务从第P分钟开始,持续时间为T分钟,其中1≤P≤N,1≤P+T-1≤N。

【输出】

输出文件仅一行,包含一个整数,表示尼克可能获得的最大空暇时间。

【样例】

lignja.inlignja.out

1564

12

16

411

85

81

115

【算法分析】

题目给定的数据规模十分巨大:

1≤K≤10000。

采用穷举方法显然是不合适的。

根据求最大的空暇时间这一解题要求,先将K个任务放在一边,以分钟为阶段,设置minute[i]表示从第i分钟开始到最后一分钟所能获得的最大空暇时间,决定该值的因素主要是从第i分钟起到第n分钟选取哪几个任务,与i分钟以前开始的任务无关。

由后往前逐一递推求各阶段的minute值:

(1)初始值minute[n+1]=0

(2)对于minute[i],在任务表中若找不到从第i分钟开始做的任务,则minute[i]比minute[i+1]多出一分钟的空暇时间;若任务表中有一个或多个从第i分钟开始的任务,这时,如何选定其中的一个任务去做,使所获空暇时间最大,是求解的关键。

下面我们举例说明。

设任务表中第i分钟开始的任务有下列3个:

任务K1P1=iT1=5

任务K2P2=iT2=8

任务K3P3=iT3=7

而已经获得的后面的minute值如下:

minute[i+5]=4,minute[i+8]=5,minute[i+7]=3

若选任务K1,则从第i分钟到第i+1分钟无空暇。

这时从第i分钟开始能获得的空暇时间与第i+5分钟开始获得的空暇时间相同。

因为从第i分钟到i+5-1分钟这时段中在做任务K1,无空暇。

因此,minute[i]=minute[i+5]=4。

同样,若做任务K2,这时的minute[i]=minute[i+8]=5

若做任务K3,这时的minute[i]=minute[1+7]=3

显然选任务K2,所得的空暇时间最大。

因此,有下面的状态转移方程:

其中,Tj表示从第i分钟开始的任务所持续的时间。

下面是题目所给样例的minute值的求解。

任务编号K

1

2

3

4

5

6

开始时间P

1

1

4

8

8

11

持续时间T

2

6

11

5

1

5

时刻I

16

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

minute[i]

0

1

2

3

4

0

1

2

3

4

5

6

1

2

3

4

选任务号k

0

0

0

0

0

6

0

0

4

0

0

0

3

0

0

2

注:

选任务号为该时刻开始做的任务之一,0表示无该时刻开始的任务。

问题所求得最后结果为从第1分钟开始到最后时刻所获最大的空暇时间minute[1]。

主要算法描述如下:

(1)数据结构

varp:

array[1..10000]ofinteger;{任务开始时间}

t:

array[1..10000]ofinteger;{任务持续时间}

minute:

array[0..10001]ofinteger;{各阶段最大空暇时间}

(2)数据读入

①readln(n,k);{读入总的工作时间n及任务k}

②读入k个任务信息

fori:

=1tokdoreadln(p[i],t[i]);{假设任务的开始时间按有小到大排列}

(3)递推求minute[i]

j:

=k;{从最后一个任务选起}

fori:

=ndownto1do

begin

minute[i]:

=0;

ifp[i]<>ithenminute[i]:

=1+minute[i+1]{无任务可选}

elsewhilep[j]=ido{有从i分钟开始的任务}

begin

ifminute[i+t[j]]>minute[i]thenminute[i]:

=minute[i+t[j]];{求最大空暇时间}

j:

=j-1;{下一个任务}

end;

end;

(4)输出解

writeln(minute[1]);

8.4书的复制

源程序名book.?

?

?

(pas,c,cpp)

可执行文件名book.exe

输入文件名book.in

输出文件名book.out

【问题描述】

现在要把m本有顺序的书分给k给人复制(抄写),每一个人的抄写速度都一样,一本书不允许给两个(或以上)的人抄写,分给每一个人的书,必须是连续的,比如不能把第一、第三、第四本书给同一个人抄写。

现在请你设计一种方案,使得复制时间最短。

复制时间为抄写页数最多的人用去的时间。

【输入】

第一行两个整数m,k;(k≤m≤500)

第二行m个整数,第i个整数表示第i本书的页数。

【输出】

共k行,每行两个整数,第i行表示第i个人抄写的书的起始编号和终止编号。

k行的起始编号应该从小到大排列,如果有多解,则尽可能让前面的人少抄写。

【样例】

book.inbook.out

9315

12345678967

89

【问题分析】

本题可以用动态规划解决,但是动态规划并不是一个聪明的方法,这个后面会提到。

不管怎样,我们还是先介绍动态规划的方法。

设f(n,k)为前n本书交由k个人抄写,需要的最短时间,则状态转移方程为

f(n,k)=min{max{f(i,k-1),

},i=1,2,…,n-1}

状态数为n·k,转移代价为O(n),故时间复杂度为O(n2·k)。

不难看出,上述方程满足四边形不等式,所以如果利用四边形不等式的性质,转移代价由平摊分析可得平均为O

(1)。

因此,时间复杂度可以降为O(n·k)。

动态规划求出的仅仅是最优值,如果要输出具体方案,还需根据动态规划计算得到的最优值,做一个贪心设计。

具体来说,设最优值为T,那么k个人,每个人抄写最多T页。

按顺序将书分配给k人去抄写,从第一个人开始,如果他还能写,就给他;否则第一个人工作分配完毕,开始分配第二个人的工作;以后再是第三个、第四个、……直至第k个。

一遍贪心结束后,具体的分配方案也就出来了。

贪心部分的复杂度是O(n)的。

从前面的分析可以看到,动态规划部分仅仅是求出了一个最优值,具体方案是通过贪心求得的。

而动态规划这部分的时间复杂度却是相当之高,所以用动态规划来求最优值是很不合算的。

可以看到,当每人抄写的页数T单调增加时,需要的人数单调减少,这就符合了二分法的基本要求。

我们可以对T二分枚举,对每个枚举的T,用贪心法既求出需要的人数又求出具体的方案。

所以,通过二分就能求得需要人数为k的最小的Tmin和相应的方案了。

时间复杂度为O(nlog2c),c为所有书本的页数和。

从这道题目的解题过程中,我们可以看到动态规划也不是万金油,有时更一般的方法却可以得到更好的结果。

8.5多米诺骨

源程序名dom.?

?

?

(pas,c,cpp)

可执行文件名dom.exe

输入文件名dom.in

输出文件名dom.out

【问题描述】

多米诺骨牌有上下2个方块组成,每个方块中有1~6个点。

现有排成行的n个多米诺骨牌如图8-1所示。

●●

●●

●●

●●

●●

上方块中点数之和记为

,下方块中点数之和记为

,它们的差为

例如在图8-1中,

=6+1+1+1=9,

=1+5+3+2=11,

=2。

每个多米诺骨牌可以旋转180°,使得上下两个方块互换位置。

编程用最少的旋转次数使多米诺骨牌上下2行点数之差达到最小。

对于图8-1中的例子,只要将最后一个多米诺骨牌旋转180°,可使上下2行点数之差为0。

【输入】

输入文件的第一行是一个正整数n(1≤n≤1000),表示多米诺骨牌数。

接下来的n行表示n个多米诺骨牌的点数。

每行有两个用空格隔开的正整数,表示多米诺骨牌上下方块中的点数a和b,且1≤a,b≤6。

【输出】

输出文件仅一行,包含一个整数。

表示求得的最小旋转次数。

【样例】

dom.indom.out

41

61

15

13

12

【问题分析】

本问题可归约为经典的背包问题。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 经管营销 > 经济市场

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1