动态规划

  动态规划与分治法一样,将问题的最优解切割成多个子问题的最优解;但与分治法不同的是,分治法要求各个子问题必须是相互独立的,而动态规划适用于子问题不是独立的情况,也就是各子问题包含公共的子子问题。在这种情况下,若用分治法则会做许多不必要的工作,即重复的求解公共的子子问题。

  采用动态规划时,由于有多个公共的子子问题,可以将各个子子问题的结果保留下来,避免重复计算。动态规划算法可以分为以下四步实现:

  1. 描述最优解的结果
  2. 递归定义最优解的值
  3. 按自底向上的方式计算最优解的值
  4. 由计算出的结果构造一个最优解

1~3步构成问题的动态规划最优解的基础,第4步在只要计算最优解的值时可以略去。

经典应用:01背包问题,最优二插查找树,最长公共子序列。

背包问题:

1.POJ1837 http://poj.org/problem?id=1837

题意:有一杆秤,在秤上挂有C(2<=C<=20)个钩子,现在想把G(2<=G<=20)个砝码挂在钩子上并使得秤保持平衡,问最多有多少种挂法?

思路:看到这题,第一个反应就是枚举暴力破解O_O,但是使用枚举的话,时间复杂度是指数型,肯定超时。我们可以定义一个变量dp[i][j],它代表当使用了前i个砝码,最好有dp[i][j]种挂法使得平衡度为j。由于距离c[i]的范围是-15~15,钩码重量的范围是1~25,钩码数量最大是20,所以平衡度的范围是-7500~7500(7500=15*25*20)。为了使平衡度不为负,我们使用范围0~15000,其中平衡度为7500时秤保持平衡。不难想到,假设 dp[i-1][j] 的值已知,设dp[i-1][j]=num,那么dp[i][ j+ w[i]*c[k] ] = dp[i-1][j] = num,所以我们可以推出dp[i][ j+ w[i]*c[k] ]= ∑(dp[i-1][j])(由递推式可以看出是dp问题,因为这个问题的最优解可以通过其各个子问题的最优解的和得到)

View Code
1 #include<iostream>  
2 using namespace std;
3 int dp[21][15001];
4 int main(int i,int j,int k)
5 {
6 int n; //挂钩数
7 int g; //钩码数
8 int c[21]; //挂钩位置
9 int w[21]; //钩码重量
10 cin>>n>>g;
11 for(i=1;i<=n;i++)
12 cin>>c[i];
13 for(i=1;i<=g;i++)
14 cin>>w[i];
15 memset(dp,0,sizeof(dp)); //达到每个状态的方法数初始化为
16 dp[0][7500]=1; //7500为天枰达到平衡状态时的平衡度
17 for(i=1;i<=g;i++)
18 for(j=0;j<=15000;j++)40
19 if(dp[i-1][j])20
for(k=1;k<=n;k++)21
dp[i][ j+w[i]*c[k] ] += dp[i-1][j]; //状态方程
22 cout<<dp[g][7500]<<endl;
23 return 0;
24 }


2.POJ1276 http://poj.org/problem?id=1276

题意:有钞票面额N(0<=N<=10)种,每种面额为Dk(0<=Dk<=1000)钞票有nk(1<=nk<=1000)张,问通过这些钞票的各种组合,求出能够得到的最接近cash(0<=cash<=100000)的总额

分析:一看就是多重背包问题,推荐看背包九讲

View Code
 1 #include <iostream>
2
3 using namespace std;
4 int V,nk[15],dk[15];
5 int f[100010];
6
7 void ZeroOnePack(int cost,int weight)
8 {
9 for (int v=V;v>=cost;v--)
10 f[v] = max(f[v],f[v-cost]+weight);
11 }
12
13 void CompletePack(int cost,int weight)
14 {
15 for (int v=cost;v<=V;v++)
16 f[v] = max(f[v],f[v-cost]+weight);
17 }
18
19 void MultiPack(int cost,int weight,int amount)
20 {
21 if (cost*amount>=V)
22 {
23 CompletePack(cost,weight);
24 return;
25 }
26 int k=1;
27 while (k<amount)
28 {
29 ZeroOnePack(k*cost,k*weight);
30 amount = amount-k;
31 k = k*2;
32 }
33 ZeroOnePack(amount*cost,amount*weight);
34 }
35
36
37 int main()
38 {
39 int N;
40 while (cin>>V)
41 {
42 memset(f,0,sizeof(f));
43 cin>>N; //货币有N种
44 for (int i=0;i<N;i++)
45 cin>>nk[i]>>dk[i];
46 if(V==0)
47 {
48 cout<<0<<endl;
49 continue;
50 }
51 for (int i=0;i<N;i++)
52 MultiPack(dk[i],dk[i],nk[i]);
53 cout<<f[V]<<endl;
54 }
55
56 return 1;
57 }

简单dp问题

POJ3267 http://poj.org/problem?id=3267

题意:给定你一个字符串,和n个单词构成的字典。让你求出在字符串中最小删去几个字母,使得剩下的字符串能够由字典中的若干个单词构成。输出最少删去的字母的个数。

分析:如果考虑第i位的字母,当然只有要和不要两种状态,这是本题的突破口,是划分状态的根本。

View Code
 1 #include <iostream>
2 #include <cstring>
3 using namespace std;
4
5 char dict[601][26], rec[301];
6 int w, l, d[601], len[601];
7 /*
8 d(i) 表示以第i个位置起并保留该位置的字母的字符串匹配时需要删的最少字符数
9 t:表为匹配单词而删的字符数(如果可以匹配)
10 */
11 int main()
12 {
13 int i, j, k, tmp, now, t;
14
15 while (cin >> w >> l)
16 {
17 scanf("%s", rec);
18
19 for (i = 0; i < w; i++)
20 {
21 scanf("%s", dict[i]);
22 len[i] = strlen(dict[i]);
23 }
24
25 memset(d, 0, sizeof(d));
26
27 //dp
28 for (i = l - 1; i >= 0; i--)
29 {
30 for (now = 601, j = 0; j < w; j++)
31 {
32 if (rec[i] != dict[j][0]) continue;
33
34 //匹配
35 k = 1;
36 t = 0;
37 tmp = i + 1;
38 while (k < len[j] && tmp < l)
39 {
40 if (rec[tmp] == dict[j][k]) k++;
41 else t++;
42
43 tmp++;
44 }
45
46 //找出所有符合要求的匹配中的最小值
47 if (k >= len[j]) now = t + d[tmp] > now ? now :t+ d[tmp];
48 }
49
50 //用符合要求的匹配的最小值和d[i+1]+1比较
51 d[i] = d[i + 1] + 1 > now ? now : d[i + 1] + 1;
52 }
53 cout << d[0] << endl;
54 }
55
56 return 0;
57 }

POJ1260 http://poj.org/problem?id=1260

题意:珍珠有(c1, c2, ..., cn)共n个品级,对应不同的价格(p1, p2, ..., pn)。购买品级为ci的珍珠需缴纳且只缴纳一次额外费用,具体值为10*pi。现购买一批珍珠,购买数量为(a1, a2, ..., an),ai代表购买品级为ci的珍珠的数量。整个数量结构可以变动,规则为:可购买需要品级的珍珠或同等数量更高品级的珍珠,但不能购买更低等级的珍珠(即低等级的珍珠可以由高等级的珍珠代替),求购买珍珠的最少花费。

分析:先将要购买的珍珠按价格从高到低排列,那么当购买第i种珍珠时有多种策略(可以由第1,.....,i种中的其中一种代替,但是必须满足一个条件,就是如果用第j(1<=j<=i-1)种珍珠代替第i种时,那么第j种珍珠必须没有被第k种珍珠所代替)。设dp[i][j]代表客户要买i种珍珠,且第j种珍珠没有被比它高级的珍珠所代替所需的最少花费,则dp[i] = min(dp[i-1][j]+ai[i]*pi[j],dp[i-1][j](ai[i]+10)*pi[i],1<=j<=i)。这是我的思路,但是后来想下总感觉不对劲,但是编的程序可以通过,囧

View Code
 1 #include <iostream>
2
3 using namespace std;
4 int ai[1001];//个数
5 int pi[1001];//价格
6 int dp[101][101];
7
8 int main()
9 {
10 int case_num,category_num;
11
12 cin>>case_num;
13 for (int case_ind=0;case_ind<case_num;case_ind++)
14 {
15 memset(dp,0,sizeof(dp));
16 cin>>category_num; //input
17 for (int i=category_num;i>0;i--)
18 cin>>ai[i]>>pi[i];
19
20 dp[1][1] = (ai[1]+10)*pi[1];
21
22 for (int i=2;i<=category_num;i++)
23 {
24 int min_price = dp[i-1][1];
25 for (int j=1;j<i;j++)
26 {
27 min_price = min(min_price,dp[i-1][j]);
28 if (ai[i]*pi[j]<(ai[i]+10)*pi[i])
29 dp[i][j] = dp[i-1][j]+ai[i]*pi[j]; //第i种被第j种替换
30 else
31 dp[i][j] = dp[i-1][j]+(ai[i]+10)*pi[i];//不被高价pearl替换
32 }
33 dp[i][i] = min(min_price,dp[i-1][i-1])+(ai[i]+10)*pi[i];
34 }
35
36 int low_price=dp[category_num][1]; //output
37 for (int i=1;i<=category_num;i++)
38 low_price = min(low_price,dp[category_num][i]);
39 cout<<low_price<<endl;
40
41 }
42 return 1;
43 }

看了下别人的代码,发现完全可以用一维的dp做。设e(i)为从品级c1至ci购买的最少花费(c1到ci是按价格从低到高排列的),则e[i]=min(e[j]+(ai[j+1]+...+ai[i]+10)*p[i]),1<=j<i.贴上他人代码:

View Code
 1 #include <stdio.h>  
2 #include <stdlib.h>
3 #include <limits.h>
4
5 int case_n;
6 int class_n;
7 int a[110];
8 int p[110];
9 long e[110];
10 long dp(){
11 int ee;
12 e[0] = (a[0]+10)*p[0];
13 for(int i = 1; i < class_n; i++){
14 e[i] = LONG_MAX;
15 for(int j = -1; j < i; j++){
16 ee = 0;
17 for(int k = j+1; k <= i; k++){
18 ee += a[k];
19 }
20 if(j == -1)
21 ee = (ee+10)*p[i];
22 else
23 ee = e[j]+(ee+10)*p[i];
24 e[i] = (ee < e[i]) ? ee : e[i];
25 }
26 }
27 return e[class_n-1];
28 }
29
30 int main(){
31 scanf("%d", &case_n);
32 for(int i = 0; i < case_n; i++){
33 scanf("%d", &class_n);
34 for(int j = 0; j < class_n; j++){
35 scanf("%d%d", &a[j], &p[j]);
36 }
37 printf("%ld\n", dp());
38 }
39 return 0;
40 }

POJ3176 http://poj.org/problem?id=3176

题意:有一金字塔形的数字序列,现要求从金字塔顶点开始依次经过各层,求最大的路径

分析:设dp[i][j]表示从顶点到第i层第j个点得最大路径,则有dp[i][j] = max(dp[i-1][j-1],dp[i-1][j])+a[i][j]。任何一本算法书的动态规划部分,肯定都有相类似的例题,相当经典

View Code
 1 #include <iostream>
2
3 using namespace std;
4 int triangle[351][351];
5 int dp[351][351];
6
7 int main()
8 {
9 int N;
10
11 cin>>N; //input
12 for (int i=1;i<=N;i++)
13 for (int j=1;j<=i;j++)
14 cin>>triangle[i][j];
15
16 dp[1][1] = triangle[1][1]; //init
17
18 for (int i=2;i<=N;i++) //dp
19 {
20 for (int j=1;j<=i;j++)
21 {
22 if(j==1) dp[i][j] = dp[i-1][j]+triangle[i][j];
23 else if(j==i) dp[i][j] = dp[i-1][j-1]+triangle[i][j];
24 else dp[i][j] = max(dp[i-1][j]+triangle[i][j],dp[i-1][j-1]+triangle[i][j]);
25 }
26 }
27
28 int highest_sum = dp[N][1]; //output
29 for(int i=1;i<=N;i++)
30 highest_sum = max(highest_sum,dp[N][i]);
31 cout<<highest_sum;
32
33 return 1;
34 }








posted @ 2011-10-17 19:54  南风又起  阅读(257)  评论(0编辑  收藏  举报