动态规划的很经典的教程Word格式.docx
- 文档编号:17383539
- 上传时间:2022-12-01
- 格式:DOCX
- 页数:128
- 大小:158.49KB
动态规划的很经典的教程Word格式.docx
《动态规划的很经典的教程Word格式.docx》由会员分享,可在线阅读,更多相关《动态规划的很经典的教程Word格式.docx(128页珍藏版)》请在冰豆网上搜索。
数塔问题,和走路问题(详见解题报告)
先确定状态的问题:
大多数都是先确定状态的。
先确定决策的问题:
背包问题。
(详见解题报告)
一般都是先从比较明显的地方入手,至于怎么知道哪个明显就是经验问题了,多做题就会发现。
(3)寻找规律法:
这个方法很简单,耐心推几组数据后,看他们的规律,总结规律间的共性,有点贪心的意思。
(4)边界条件法
找到问题的边界条件,然后考虑边界条件与它的领接状态之间的关系。
这个方法也很起效。
(5)放宽约束和增加约束
这个思想是在陈启锋的论文里看到的,具体内容就是给问题增加一些条件或删除一些条件使问题变的清晰。
第二节动态规划分类讨论
这里用状态维数对动态规划进行了分类:
1.状态是一维的
1.1下降/非降子序列问题:
问题描述:
{挖掘题目的本质,一但抽象成这样的描述就可以用这个方法解}
在一个无序的序列a1,a2,a3,a4…an里,找到一个最长的序列满足:
ai<
=aj<
=ak…<
=am,且i<
j<
k…<
m.(最长非降子序列)或ai>
aj>
ak…>
am,且i>
j>
k…>
m.(最长下降子序列)。
问题分析:
如果前i-1个数中用到ak(ak>
ai或ak<
=ai)构成了一个的最长的序列加上第I个数ai就是前i个数中用到i的最长的序列了。
那么求用到ak构成的最长的序列有要求前k-1个数中……
从上面的分析可以看出这样划分问题满足最优子结构,那满足无后效性么?
显然对于第i个数时只考虑前i-1个数,显然满足无后效性,可以用动态规划解。
分析到这里动态规划的三要素就不难得出了:
如果按照序列编号划分阶段,设计一个状态opt[i]表示前i个数中用到第i个数所构成的最优解。
那么决策就是在前i-1个状态中找到最大的opt[j]使得aj>
ai(或aj<
=ai),opt[j]+1就是opt[i]的值;
用方程表示为:
{我习惯了这种写法,但不是状态转移方程的标准写法}
opt[i]=max(opt[j])+1
(0<
=j<
i且aj<
=ai)
{最长非降子序列}
i且aj>
ai)
{最长下降子序列}
实现求解的部分代码:
opt[0]:
=maxsize;
{maxsize为maxlongint或-maxlongint}
for
i:
=1tondo
forj:
=0toi-1do
if(a[j]>
a[i])and(opt[j]+1>
opt[i])then
opt[i]:
=opt[j]+1;
ans:
=-maxlongint;
fori:
ifopt[i]>
ansthenans:
=opt[i];
{ans为最终解}
复杂度:
从上面的实现不难看出时间复杂度为O(N2),空间复杂度O(N);
例题1拦截导弹(missile.pas/c/cpp)来源:
NOIP1999(提高组)第一题
【问题描述】
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。
但是这种导弹拦截系统有一个缺陷:
虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
某天,雷达捕捉到敌国的导弹来袭。
由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
【输入文件】missile.in
单独一行列出导弹依次飞来的高度。
【输出文件】missile.out
两行,分别是最多能拦截的导弹数,要拦截所有导弹最少要配备的系统数
【输入样例】
38920715530029917015865
【输出样例】
6
2
【问题分析】
有经验的选手不难看出这是一个求最长非升子序列问题,显然标准算法是动态规划。
以导弹依次飞来的顺序为阶段,设计状态opt[i]表示前i个导弹中拦截了导弹i可以拦截最多能拦截到的导弹的个数。
状态转移方程:
(h[i]>
=h[j],0=<
i)
{h[i]存,第i个导弹的高度}
最大的opt[i]就是最终的解。
这只解决了第一问,对于第二问最直观的方法就是求完一次opt[i]后把刚才要打的导弹去掉,在求一次opt[i]直到打完所有的导弹,但这样做就错了。
不难举出反例:
61732
错解:
632/1/7
正解:
61/732
其实认真分析一下题就回发现:
每一个导弹最终的结果都是要被打的,如果它后面有一个比它高的导弹,那打它的这个装置无论如何也不能打那个导弹了,经过这么一分析,这个问题便抽象成在已知序列里找最长上升序列的问题。
求最长上升序列和上面说的求最长非升序列是一样的,这里就不多说了。
时间复杂度为O(N2),空间复杂度为O(N)。
【源代码】
programmissile;
const
fin='
missile.in'
;
fout='
missile.out'
maxn=10000;
var
a,opt:
array[0..maxn]oflongint;
n,anslen,anstime:
longint;
procedure
init;
x:
begin
assign(input,fin);
reset(input);
assign(output,fout);
rewrite(output);
n:
=0;
repeat
inc(n);
read(a[n]);
untilseekeof;
end;
proceduremain;
i,j:
fillchar(opt,sizeof(opt),0);
a[0]:
=maxlongint;
=i-1downto0do
if(a[j]>
=a[i])and(opt[j]+1>
anslen:
anslenthen
if(a[j]<
anstime:
anstimethen
procedureprint;
writeln(anslen);
writeln(anstime);
close(input);
close(output);
main;
print;
end.
例题二合唱队形(chorus.pas/c/cpp)来源:
NOIP2004(提高组)第一题
N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。
合唱队形是指这样的一种队形:
设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK,
则他们的身高满足T1<
...<
Ti>
Ti+1>
…>
TK(1<
=i<
=K)。
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
【输入文件】
输入文件chorus.in的第一行是一个整数N(2<
=N<
=100),表示同学的总数。
第一行有n个整数,用空格分隔,第i个整数Ti(130<
=Ti<
=230)是第i位同学的身高(厘米)。
【输出文件】
输出文件chorus.out包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
【样例输入】
8
186186150200160130197220
【样例输出】
4
【数据规模】
对于50%的数据,保证有n<
=20;
对于全部的数据,保证有n<
=100。
出列人数最少,也就是说留的人最多,也就是序列最长。
这样分析就是典型的最长下降子序列问题。
只要枚举每一个人站中间时可以的到的最优解。
显然它就等于,包括他在内向左求最长上升子序列,向右求最长下降子序列。
我们看一下复杂度:
计算最长下降子序列的复杂度是O(N2),一共求N次,总复杂度是O(N3)。
这样的复杂度对于这个题的数据范围来说是可以AC的。
但有没有更好的方法呢?
其实最长子序列只要一次就可以了。
因为最长下降(上升)子序列不受中间人的影响。
只要用OPT1求一次最长上升子序列,OPT2求一次最长下降子序列。
这样答案就是N-max(opt1[i]+opt2[i]-1).
复杂度由O(N3)降到了O(N2)。
programchorus;
chorus.in'
chorus.out'
maxn=200;
opt1,opt2,a:
n,ans:
procedureinit;
readln(n);
read(a[i]);
a[i])and(opt1[j]+1>
opt1[i])then
opt1[i]:
=opt1[j]+1;
a[n+1]:
=ndownto1do
=i+1ton+1do
a[i])and(opt2[j]+1>
opt2[i])then
opt2[i]:
=opt2[j]+1;
ifopt1[i]+opt2[i]>
ansthen
=opt1[i]+opt2[i];
writeln(n-ans+1);
例题3BuyLowBuyLower(buylow.pas/c/cpp)来源:
USACO4-3-1
“逢低吸纳”是炒股的一条成功秘诀。
如果你想成为一个成功的投资者,就要遵守这条秘诀:
"
逢低吸纳,越低越买"
这句话的意思是:
每次你购买股票时的股价一定要比你上次购买时的股价低.按照这个规则购买股票的次数越多越好,看看你最多能按这个规则买几次。
给定连续的N天中每天的股价。
你可以在任何一天购买一次股票,但是购买时的股价一定要比你上次购买时的股价低。
写一个程序,求出最多能买几次股票。
以下面这个表为例,某几天的股价是:
天数1
2
3
4
5
6
7
8
9
101112
股价686954646864706778629887
这个例子中,聪明的投资者(按上面的定义),如果每次买股票时的股价都比上一次买时低,那么他最多能买4次股票。
一种买法如下(可能有其他的买法):
天数2
10
股价69686462
【输入文件】buylow.in
第1行:
N(1<
=N<
=5000),表示能买股票的天数。
第2行以下:
N个正整数(可能分多行),第i个正整数表示第i天的股价.这些正整数大小不会超过longint(pascal)/long(c++).
【输出文件】buylow.out
只有一行,输出两个整数:
能够买进股票的天数长度达到这个值的股票购买方案数量
在计算解的数量的时候,如果两个解所组成的字符串相同,那么这样的两个解被认为是相同的(只能算做一个解)。
因此,两个不同的购买方案可能产生同一个字符串,这样只能计算一次。
12
6869546468647067
78629887
42
从题目描述就可以看出这是最长下降子序列问题,于是求解第一问不是难事,以天数为阶段,设计状态opt[i]表示前i天中要买第i天的股票所能得到的最大购买次数。
(a[i]>
=a[j],0=<
{a[i]存第i天股价}
第二问呢,稍麻烦一点。
从问题的边界出发考虑问题,当第一问求得一个最优解opt[i]=X时,
在1到N中如果还有另外一个opt[j]=x那么选取J的这个方案肯定是成立的。
是不是统计所有的opt[j]=x的个数就是解了呢?
显然没那么简单,因为选取J这天的股票构成的方案不见得=1,看下面一个例子:
5 6
4 3 12
方案一:
5431
方案二:
5432
方案三:
6431
方案四:
6432
x=4
也就是所虽然opt[5]=X和opt[6]=X但个数只有两个,而实际上应该有四个方案,但在仔细观察发现,构成opt[5]=x的这个方案中opt[j]=x-1的方案数有两个,opt[j]=x-2的有一个,opt[j]=x-3的有一个……
显然这是满足低归定义的设计函数F(i)表示前I张中用到第i张股票的方案数。
递推式:
1
(i=0)
F(i)=
Sum(F(j))
=n,a[j]>
a[i],opt[j]=opt[i]-1)
{sum代表求和}
答案=sum(F(j))
{0<
=n,opt[j]=x}
求解第一问时间复杂度是O(N2),求解第二问如果用递推或递归+记忆化时间复杂度仍为O(N2)但要是用赤裸裸的递归那就复杂多了……,因为那样造成了好多不必要的计算。
你认为这样做就解决了这道题目了么?
还没有,还要注意一些东西:
(1)如果有两个方案中出现序列一样,视为一个方案,要需要加一个数组next用next[i]记录和第i个数情况一样(即:
opt[i]=opt[j]且a[i]=a[j])可看做一个方案的最近的位置。
递推时j只要走到next[i]即可。
(2)为了方便操作可以将a[n+1]赋值为-maxlongint这样可以认为第n+1个一定可以买,答案就是sum(F(n+1))。
(3)看数据规模显然要用高精度。
注:
USACO上最后一个点错了。
我在程序中做了处理。
programbuylow;
buylow.in'
buylow.out'
maxn=5010;
maxsize=10;
jz=100000000;
type
arrtype=array[0..maxsize]oflongint;
a,opt,next:
F:
array[0..maxn]ofarrtype;
ifn=5then
{最后一个点错了,我只好这么写了}
writeln('
25'
);
halt;
procedureHinc(varx:
arrtype;
y:
arrtype);
i,z:
z:
=1tomaxsizedo
=zdivjz+x[i]+y[i];
x[i]:
=zmodjz;
=1ton+1do
opt[i])
then
j:
=i-1;
while(j>
0)and((opt[i]<
>
opt[j])or(a[i]<
a[j]))do
dec(j);
next[i]:
=j;
F[0,1]:
=1;
=i-1downtonext[i]do
if(opt[j]=opt[i]-1)and(a[j]>
a[i])then
Hinc(F[i],F[j]);
i,top,m:
write(opt[n+1]-1,'
'
top:
=maxsize;
while(top>
1)and(F[n+1][top]=0)do
dec(top);
write(F[n+1,top]);
=top-1downto1do
m:
=F[n+1,i];
whilem<
maxsizediv10do
write('
0'
=m*10;
write(F[n+1,i]);
writeln;
例题4船(ships.pas/c/cpp)来源:
《奥赛经典》(提高篇)
PALMIA国家被一条河流分成南北两岸,南北两岸上各有N个村庄。
北岸的每一个村庄有一个唯一的朋友在南岸,且他们的朋友村庄彼此不同。
每一对朋友村庄想要一条船来连接他们,他们向政府提出申请以获得批准。
由于河面上常常有雾,政府决定禁止船只航线相交(如果相交,则很可能导致碰船)。
你的任务是编写一个程序,帮助政府官员决定批准哪些船只航线,使得不相交的航线数目最大。
【输入文件】ships.in
输入文件由几组数据组成。
每组数据的第一行有2个整数X,Y,中间有一个空格隔开,X代表PALMIA河的长度(10<
=X<
=6000),Y代表河的宽度(10<
=Y<
=100)。
第二行包含整数N,表示分别坐落在南北两岸上的村庄的数目(1<
=5000)。
在接下来的N行中,每一行有两个非负整数C,D,由一个空格隔开,分别表示这一对朋友村庄沿河岸与PALMIA河最西边界的距离(C代表北岸的村庄,D代表南岸的村庄),不存在同岸又同位置的村庄。
最后一组数据的下面仅有一行,是两个0,也被一空格隔开。
【输出文件】ships.out
对输入文件的每一组数据,输出文件应在连续的行中表示出最大可能满足上述条件的航线的数目。
304
7
224
26
103
1512
98
1717
00
这道题目
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 动态 规划 经典 教程