NOIP复习动态规划.docx
《NOIP复习动态规划.docx》由会员分享,可在线阅读,更多相关《NOIP复习动态规划.docx(53页珍藏版)》请在冰豆网上搜索。
NOIP复习动态规划
[NOIP复习]第三章:
动态规划
分类:
NOIP Wikioi ACM-ICPC/蓝桥杯/其他大学竞赛 动态规划2014-09-0108:
15 458人阅读 评论(0) 收藏 举报
目录(?
)[+]
一、背包问题
最基础的一类动规问题,相似之处在于给n个物品或无穷多物品或不同种类的物品,每种物品只有一个或若干个,给一个背包装入这些物品,要求在不超出背包容量的范围内,使得获得的价值或占用体积尽可能大,这一类题的动规方程f[i]一般表示剩余容量为i时取得的最大价值或最大占用体积,或者有多维状态,分别表示不同种物品的剩余量
1、Wikioi1014装箱问题
题目描述 Description
有一个箱子容量为V(正整数,0<=V<=20000),同时有n个物品(0<n<=30),每个物品有一个体积(正整数)。
要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。
输入描述 InputDescription
一个整数v,表示箱子容量
一个整数n,表示有n个物品
接下来n个整数,分别表示这n 个物品的各自体积
输出描述 OutputDescription
一个整数,表示箱子剩余空间。
样例输入 SampleInput
24
6
8
3
12
7
9
7
样例输出 SampleOutput
0
一道经典的背包动规,用数组f[]进行动规,f[v]=剩余容量为v时可以利用的最大体积,那么可以在每次输入一个物品体积cost时遍历剩余容量状态,当前状态的剩余容量为v时,可以选择装入物品(装入物品则当前状态可以利用的体积为f[v-cost]+cost)或不装入物品,推出动规方程:
f[v]=max{f[v-cost]+cost}
[cpp] viewplaincopyprint?
1.#include
2.#include
3.
4.#define MAXN 30000
5.
6.int f[MAXN]; //f[i]=剩余体积为i时装入物品的最大体积
7.
8.int max(int a,int b)
9.{
10. if(a>b) return a;
11. return b;
12.}
13.
14.int main()
15.{
16. int v,n,cost;
17. scanf("%d%d",&v,&n);
18. for(int i=1;i<=n;i++)
19. {
20. scanf("%d",&cost);
21. for(int j=v;j>=cost;j--)
22. f[j]=max(f[j],f[j-cost]+cost);
23. }
24. printf("%d\n",v-f[v]);
25. return 0;
26.}
2、Wikioi1068乌龟棋
题目描述 Description
小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。
乌龟棋的棋盘是一行N个格子,每个格子上一个分数(非负整数)。
棋盘第1格是唯一的起点,第N格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。
……12345……N乌龟棋中M张爬行卡片,分成4种不同的类型(M张卡片中不一定包含所有4种类型的卡片,见样例),每种类型的卡片上分别标有1、2、3、4四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。
游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。
游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。
玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。
很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。
现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?
输入描述 InputDescription
输入的每行中两个数之间用一个空格隔开。
第1行2个正整数N和M,分别表示棋盘格子数和爬行卡片数。
第2行N个非负整数,a1a2……aN
,其中ai表示棋盘第i个格子上的分数。
第3行M个整数,b1b2……bM
,表示M张爬行卡片上的数字。
输入数据保证到达终点时刚好用光M张爬行卡片,即N-1=∑(1->M)bi
输出描述 OutputDescription
输出一行一个整数
样例输入 SampleInput
13 8
4 96 10 64 55 13 94 53 5 24 89 8 30
1 1 1 1 1 2 4 1
样例输出 SampleOutput
455
数据范围及提示 DataSize&Hint
【数据范围】
对于30%的数据有1 ≤ N≤ 30,1 ≤M≤ 12。
对于50%的数据有1 ≤ N≤ 120,1 ≤M≤ 50,且4 种爬行卡片,每种卡片的张数不会超
过20。
对于100%的数据有1 ≤ N≤ 350,1 ≤M≤ 120,且4 种爬行卡片,每种卡片的张数不会
超过40;0 ≤ ai ≤ 100,1 ≤ i ≤ N;1 ≤ bi ≤ 4,1 ≤ i ≤M。
输入数据保证N−1=ΣM
i b
1
可以说这是一道背包的变形题,不再是只有单一的状态(剩余体积),实际上因为卡片分种类,导致状态变成了4个:
4种爬行卡片分别剩余的张数,则可用数组f[][][][]来进行动规,f[i][j][k][h]=1、2、3、4号卡片各用掉i,j,k,h张时,下棋获得的最大分数,则每次从小到大动规,经过状态f[i][j][k][h]时,考虑用掉一张1或2或3或4号卡片,那么这次操作之前得到的分数就是f[i-1][k][j][h]或f[i][k-1][j][h]或f[i][k][j-1][h]或f[i][k][j][h-1](注:
上次操作得到的分数并不包含上次操作后到达的格子分数),然后再加上上次操作后到达的格子分数,这次操作到达的格子分数就等到下次操作时再算。
最后输出f[card[1]][card[2]][card[3]][card[4]]+map[n],card[x]=第x种卡片的张数,map[n]=终点的分数。
当然也可以先算上起点的格子分数,每次决策时不加上次操作到达的格子分数,而是加上本次操作到达的格子分数,或许更便于理解
[cpp] viewplaincopyprint?
1.#include
2.#include
3.
4.#define MAXM 40
5.#define MAXN 400
6.
7.int f[MAXM][MAXM][MAXM][MAXM]; //f[i][j][k][h]=1 2 3 4四种卡片各用了i,j,k,h张时的最高分数
8.int map[MAXN]; //棋盘上的分数
9.
10.int max(int a,int b)
11.{
12. if(a>b) return a;
13. return b;
14.}
15.
16.int main()
17.{
18. int n,m;
19. int card[5]={0};
20. scanf("%d%d",&n,&m);
21. for(int i=1;i<=n;i++)
22. scanf("%d",&map[i]);
23. for(int i=0;i24. {
25. int kind;
26. scanf("%d",&kind);
27. card[kind]++;
28. }
29. for(int i=0;i<=card[1];i++)
30. for(int j=0;j<=card[2];j++)
31. for(int k=0;k<=card[3];k++)
32. for(int h=0;h<=card[4];h++)
33. {
34. int dis=i*1+j*2+k*3+h*4; //dis=当前用了i,j,k,h张牌后乌龟棋移动的距离
35. if(i>=1) f[i][j][k][h]=max(f[i][j][k][h],f[i-1][j][k][h]+map[1+(i-1)*1+j*2+k*3+h*4]);
36. if(j>=1) f[i][j][k][h]=max(f[i][j][k][h],f[i][j-1][k][h]+map[1+i*1+(j-1)*2+k*3+h*4]);
37. if(k>=1) f[i][j][k][h]=max(f[i][j][k][h],f[i][j][k-1][h]+map[1+i*1+j*2+(k-1)*3+h*4]);
38. if(h>=1) f[i][j][k][h]=max(f[i][j][k][h],f[i][j][k][h-1]+map[1+i*1+j*2+k*3+(h-1)*4]);
39. }
40. printf("%d\n",f[card[1]][card[2]][card[3]][card[4]]+map[n]);
41. return 0;
42.}
3、POJ1276CashMachine(多重背包经典题)
http:
//poj.org/problem?
id=1276
题目大意:
要用n种钱币凑面额cash,要取凑到的面额必须小于等于Cash,给定每种钱币的面值和张数,求最多能凑到的面额。
经典的多重背包题,可以将每种钱币当成物品,钱币的体积和价值均为它的面值。
[cpp] viewplaincopyprint?
1.#include
2.#include
3.#include
4.#include
5.
6.#define MAXN 11
7.#define MAXV 100010
8.
9.using namespace std;
10.
11.struct Thing
12.{
13. int n,v,w; //n件,价值为v,体积为w,在这个题中v=w
14.}things[MAXN];//保存每种纸币个数
15.
16.bool canGet[MAXV]; //canGet[i]=true表示面额i可以被取到
17.
18.int main()
19.{
20. int cash,n;
21. while(scanf("%d%d",&cash,&n)!
=EOF)
22. {
23. int i,j,ans=0; //要取的面额为cash,n种钱币
24. for(int i=1;i<=n;i++)
25. {
26. scanf("%d%d",&things[i].n,&things[i].v);
27. things[i].w=things[i].v;
28. }
29. if(!
cash||!
n)
30. {
31. printf("0\n");
32. continue;
33. }
34. memset(canGet,false,sizeof(canGet));
35. canGet[0]=true; //面额为0当然可以取到
36. for(i=1,ans=0;i<=n;i++) //正在取第i种钱币
37. for(j=ans;j>=0;j--) //取到的面额为j
38. if(canGet[j]) //面额j可以取到
39. for(int k=1;k<=things[i].n;k++) //第i种物品取n个
40. {
41. int temp=j+k*things[i].w;
42. if(temp>cash) //取的面额超过了cash,太多了
43. break;
44. canGet[temp]=true;
45. if(temp>ans) ans=temp;
46. }
47. cout<48. }
49. return 0;
50.}
4、POJ1742Coins(带优化的多重背包)
http:
//poj.org/problem?
id=1742
楼教主的经典题,题目大意是要用多种钱币凑出一个面额,这个面额小于等于v,给出每种钱币的面值和个数,求最终能凑出多少种面额
[cpp] viewplaincopyprint?
1.#include
2.#include
3.#include
4.#include
5.#include
6.
7.#define MAXN 110
8.#define MAXV 100100
9.
10.using namespace std;
11.
12.struct Thing
13.{
14. int n,v,w; //个数为n,价值为v,体积为w
15.}coins[MAXN];
16.
17.bool canGet[MAXV]; //canGet[i]=true表示面额i可以被取到
18.int n,v;
19.
20.void ZeroOnePack(int cost) //01背包
21.{
22. for(int i=v;i>=cost;i--)
23. canGet[i]|=canGet[i-cost];
24.}
25.
26.void CompletePack(int cost) //完全背包
27.{
28. for(int i=cost;i<=v;i++)
29. canGet[i]|=canGet[i-cost];
30.}
31.
32.void MultiplePack(int cost,int amount) //多重背包
33.{
34. if(cost*amount>=v)
35. {
36. CompletePack(cost); //转换为完全背包问题
37. return;
38. }
39. int k=1; //拿k件同样的物品
40. while(k41. {
42. ZeroOnePack(k*cost);
43. amount-=k;
44. k<<=1; //k<-k*2
45. }
46. ZeroOnePack(amount*cost);
47.}
48.
49.int main()
50.{
51. while(scanf("%d%d",&n,&v))
52. {
53. int ans=0;
54. if(!
n||!
v) break;
55. for(int i=1;i<=n;i++) scanf("%d",&coins[i].w);
56. for(int i=1;i<=n;i++) scanf("%d",&coins[i].n);
57. memset(canGet,false,sizeof(canGet));
58. canGet[0]=true;
59. for(int i=1;i<=n;i++) //当前正在尝试凑的硬币为第i个硬币
60. if(coins[i].n) //这个硬币有剩余
61. MultiplePack(coins[i].w,coins[i].n);
62. for(int i=1;i<=v;i++) //面额为i
63. if(canGet[i]) //面额为i的可以取到
64. ans++;
65. cout<66. }
67. return 0;
68.}
二、区间型动态规划
这一类题目的相似之处在于动规方程f[i]表示以i结尾的价值/数量等等的大小,在决策时寻找i之前的位置j,使得f[i]取得最值
3、Wikioi1044拦截导弹
题目描述 Description
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。
但是这种导弹拦截系统有一个缺陷:
虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
某天,雷达捕捉到敌国的导弹来袭。
由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入描述 InputDescription
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数)
输出描述 OutputDescription
输出这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
样例输入 SampleInput
38920715530029917015865
样例输出 SampleOutput
6
2
数据范围及提示 DataSize&Hint
导弹的高度<=30000,导弹个数<=20
可以把导弹的高度化为一个数字序列,由题意可知,一个导弹拦截的目标必为原序列的不上升子序列,要想让拦截目标个数尽量多,就要求这个不上升子序列是最长的,换句话说,第一问就是求不上升子序列,第二问略微麻烦点,要让所有目标都被打而且导弹尽量少,那么需要每个导弹不光打一个目标,而且要把以这个目标为头的不上升子序列都打到,如图,绿色的线就是不上升子序列,红色的线是严格上升子序列,很明显,第二问要求最长严格上升子序列,这个子序列中的所有元素都需要一发导弹,而它们连接的不上升子序列都能被这些导弹打到,第二问的答案就是最长严格上升子序列。
[cpp] viewplaincopyprint?
1.#include
2.#include
3.#include
4.
5.#define MAXN 1000
6.
7.int up[MAXN],dn[MAXN];
8.int high[MAXN],cnt=0;
9.int maxDN=-1,maxUP=-1;
10.
11.int max(int a,int b)
12.{
13. if(a>b) return a;
14. return b;
15.}
16.
17.int main()
18.{
19. while(scanf("%d",&high[++cnt])!
=EOF);
20. for(int i=1;i<=cnt;i++)
21. for(int j=1;j
22. {
23. if(high[i]>high[j])
24. {
25. up[i]=max(up[i],up[j]+1);
26. }
27. if(high[i]<=high[j])
28. {
29. dn[i]=max(dn[i],dn[j]+1);
30. }
31. }
32. for(int i=1;i<=cnt;i++)
33. {
34. maxDN=max(maxDN,dn[i]);
35. maxUP=max(maxUP,up[i]);
36. }
37. printf("%d\n%d\n",maxDN,maxUP+1);
38. return 0;
39.}
4、Wikioi3027线段覆盖2
题目描述 Description
数