动态规划例题总结

Uva1025
题意:这个城市有n个车站,每隔一段时间会从所有车站的一遍发出一列列车,有一个要从第一个车站出发,目的是在时间T会见车站n 的一个人,需要换乘,问在不同车站换成所需要等待的最短时间是多少

解法:d(i,j)表示在时刻i,你在j车站所需要等待的最长时间,有三种决策,1. 等一分钟、2. 搭乘往右开的车、3. 搭乘往左开的车

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 using namespace std;
 5 const int inf = 0x3fffffff;
 6 int dp[200 + 5][50 + 5];//在时间t,车站i应该等待的最长时间
 7 int has_train[205][55][2], t[50 + 5];//has_train表示在时间t车站i是否有列车
 8 int d[50 + 5], e[50 + 5];//往右开的第一班车,往左开的第一班车
 9 
10 int main() {
11     int n, T, tmp = 1;
12     while (scanf("%d%d", &n, &T)&&n) {
13         memset(dp, 0, sizeof(dp)); memset(has_train, 0, sizeof(has_train));
14         memset(t, 0, sizeof(t)); memset(d, 0, sizeof(d)); memset(e, 0, sizeof(e));
15 
16         for (int i = 1; i <= n - 1; i++) scanf("%d", &t[i]);
17 
18         int m1; scanf("%d", &m1);
19         for (int i = 1; i <= m1; i++) {
20             scanf("%d", &d[i]);
21             int tmp = d[i];
22             for (int j = 1; j <= n - 1; j++) {
23                 if (tmp <= T) has_train[tmp][j][0] = 1;
24                 tmp += t[j];
25             }
26         }
27         int m2; scanf("%d", &m2);
28         for (int i = 1; i <= m2; i++) {
29             scanf("%d", &e[i]);
30             int tmp = e[i];
31             for (int j = n - 1; j >= 1; j--) {
32                 if (tmp <= T) has_train[tmp][j + 1][1] = 1;
33                 tmp += t[j];
34             }
35         }
36 
37         for (int i = 1; i <= n - 1; i++)dp[T][i] = inf;
38         dp[T][n] = 0;
39 
40         for (int i = T - 1; i >= 0; i--)
41             for (int j = 1; j <= n; j++) {
42                 dp[i][j] = dp[i + 1][j] + 1;//时间先前流动一秒
43                 if (j < n&&has_train[i][j][0] && i + t[j] <= T)
44                     dp[i][j] = min(dp[i][j], dp[i + t[j]][j + 1]);
45                 if (j > 1 && has_train[i][j][1] && i + t[j - 1] <= T)
46                     dp[i][j] = min(dp[i][j], dp[i + t[j - 1]][j - 1]);
47             }
48 
49         printf("Case Number %d: ", tmp++);
50         if (dp[0][1] >= inf) printf("impossible
");
51         else printf("%d
", dp[0][1]);
52     }
53     return 0;
54 }

Uva437
题意:有n种正方体,每种都有无穷多个。要求选一些立方体摞成一根尽量高的柱子,使得每个立方体的地面长宽严格小于它下方立方体的地面长宽

解法:见代码

 1 /*
 2 思路:
 3 因为a和b的值会很大,所以我们不能直接用d(a,b)来表示状态值;
 4 所以我们(idx,h)来表示这个状态,h存放的是立方体的的三条高,知道其中的一条我们就能知道对应的面
 5 idx表示立方体的编号
 6 ans就是我们要求的那个结果,即最大高度,状态转移就是简单的拿或者不拿那个立方体
 7 */
 8 
 9 #include<cstdio>
10 #include<algorithm>
11 #include<cstring>
12 using namespace std;
13 const int maxn = 30 + 5;
14 int n, blocks[maxn][3], d[maxn][3];
15 
16 void get_dimensions(int* v, int b, int dim) {
17     int idx = 0;
18     for (int i = 0; i < 3; i++) {
19         if (i != dim) v[idx++] = blocks[b][i];
20     }
21 }
22 
23 int dp(int i,int j) {
24     int&ans = d[i][j];
25     if (ans > 0)return ans;
26     ans = 0;
27     int v[2], v2[2];
28     get_dimensions(v, i, j);
29     for(int a=0;a<n;a++)
30     for(int b=0;b<3;b++){//a和b是底面的边长
31         get_dimensions(v2, a, b);
32         if (v2[0] < v[0] && v2[1] < v[1]) ans = max(ans, dp(a, b));
33     }
34     ans += blocks[i][j];
35     return ans;
36 }
37 
38 int main() {
39     int kase = 0;
40     while (scanf("%d", &n) && n) {
41         for (int i = 0; i < n; i++) {
42             for (int j = 0; j < 3; j++) 
43                 scanf("%d", &blocks[i][j]);
44             sort(blocks[i], blocks[i] + 3);
45         }
46         memset(d, 0, sizeof(d));
47         int ans = -1;
48         for (int i = 0; i < n; i++) {
49             for (int j = 0; j < 3; j++) {
50                 ans = max(ans, dp(i, j));
51             }
52         }
53         printf("Case %d: maximum height = %d
", ++kase, ans);
54     }
55     return 0;
56  }

Uva116
题意:有一个m行n列的整数矩阵,从第一列任何一个位置出发每次往↗↘或者→走一格,最终到达最后一列,要求经过的整数之和最小。整个矩形是环形的,即第一行的上一行是最后一行,最后一行的下一行是第一行。输出路径上每列的行号。多解释输出字典序最小的

解法:见代码

 1 /*
 2 多阶段决策问题,重点是行和列的处理,注意看一下
 3 还有一个重点是结点路径的保存
 4 */
 5 #include<cstdio>
 6 #include<algorithm>
 7 #include<cstring>
 8 using namespace std;
 9 const int inf = 0x3fffffff;
10 int a[1010][1010], d[1010][1010], nex[1010][1010];
11 
12 int main() {
13     int n, m;
14     while (scanf("%d%d", &m, &n) ==2 && m ) {//m行,n列
15         memset(a, 0, sizeof(a)); memset(d, 0, sizeof(d));
16         memset(nex, 0, sizeof(nex));
17 
18         for (int i = 0; i < m; i++) 
19             for (int j = 0; j < n; j++) {
20                 scanf("%d", &a[i][j]);
21             }
22 
23         int ans = inf, first = 0;
24         for (int j = n - 1; j >= 0; j--) {//从每一行开始
25             for (int i = 0; i < m; i++) {//从每一列开始
26                 if (j == n - 1)d[i][j] = a[i][j];//设置初始值
27                 else {
28                     int row[3] = { i,i - 1,i + 1 };//三个方向
29                     if (i == 0)row[1] = m - 1;//上一行是下边界
30                     if (i == m - 1)row[2] = 0;//下一行是上边界
31                     sort(row, row + 3);
32                     d[i][j] = inf;
33                     for (int k = 0; k < 3; k++) {
34                         int v = d[row[k]][j + 1] + a[i][j];
35                         //↓↓↓nex[i][j]表示从(i,j)出发的下一个坐标
36                         if (v < d[i][j]) { d[i][j] = v; nex[i][j] = row[k]; }
37                     }
38                 }
39                 if (j == 0 && d[i][j] < ans) { ans = d[i][j]; first = i; }//更新出发点
40             }
41         }
42 
43         printf("%d", first + 1);
44         for (int i = nex[first][0], j = 1; j < n; i = nex[i][j], j++) {
45             printf(" %d", i + 1);
46         }printf("
%d
", ans);
47     }
48     return 0;
49 }

Uva12563
题意:有n首歌,剩余时间为UP,每首歌都有一个时间长度,要求你在剩余时间内尽可能多的唱歌,同时要求唱的时间尽可能的长

解法:01背包,双状态

 1 /*
 2 背包问题的变式,这道题的解法涉及到了一个dp中核心的解法:状态
 3 注意枚举上界UP要-1
 4 */
 5 #include<cstdio>
 6 #include<algorithm>
 7 #include<cstring>
 8 using namespace std;
 9 typedef long long ll;
10 const int maxn = 180 * 50 + 5;
11 struct node {
12     int num, len;
13     bool operator <  (const node& rhs) const {
14         return num < rhs.num || (num == rhs.num&&len < rhs.len);
15     }
16 };
17 node dp[maxn];
18 ll t[maxn];
19 
20 int main() {
21     int T; scanf("%d", &T);
22     int kase = 1;
23     while (T--) {
24         ll n, UP; scanf("%lld%lld", &n, &UP);
25         memset(t, 0, sizeof(t)); memset(dp, 0, sizeof(dp));
26         ll sum = 0;
27         for (int i = 1; i <= n; i++) scanf("%lld", &t[i]), sum += t[i];
28         UP -= 1;
29 
30         for (int i = 1; i <= n; i++) {
31             for (int j = UP; j >= 0; j--) {
32                 if (j < t[i])break;//剩余时间都不够t[i]了,没必要再选择了
33                 node tmp = { dp[j - t[i]].num + 1,dp[j - t[i]].len + t[i] };
34                 dp[j] = max(dp[j], tmp);//选取更优状态
35             }
36         }
37 
38         printf("Case %d: %d %d
", kase++, dp[UP].num + 1, dp[UP].len + 678);
39     }
40     return 0;
41 }

Uva1625
题意:有两个字符串,每次可以选两个首位的字符将他们加入到新串的尾部,对于每个颜色来说(每个颜色就是每个字符),其跨度L(c)等于最大位置和最小位置之差。找到一种合并方式,使得所有的L(c)总和最小

解法:见代码

 1 /*
 2 非常好的思路题,多多品味
 3 */
 4 #include<cstdio>
 5 #include<algorithm>
 6 #include<cstring>
 7 using namespace std;
 8 const int maxn = 5e3 + 5;
 9 const int inf = 0x3fffffff;
10 int dp[maxn][maxn], cnt[maxn][maxn];
11 int bf[100], bs[100], ef[100], es[100];
12 char f[maxn], s[maxn];
13 //dp[i][j]为从第一个颜色序列中拿走前i个颜色,从第二个颜色中拿走前j个颜色时合并产生的序列的最小lc之和
14 //cnt[i][j]为分别取走i个颜色和j个颜色时还有多少种颜色已经出现但尚未结束
15 //bf[c]为f串中c字母出现的第一个位置,es[c]为f串中c字母出现的最后一个位置
16 //ef同上,只不过对应s串中的情况
17 
18 int main() {
19     int T; scanf("%d", &T);
20     while (T--) {
21         scanf("%s%s", f + 1, s + 1);
22         int lenf = strlen(f+1), lens = strlen(s+1);
23         for (char c = 'A'; c <= 'Z'; c++) {
24             bf[c] = bs[c] = inf;
25             ef[c] = es[c] = 0;
26         }
27 
28         //处理开始值和终点值,注意处理开始值的时候采用最值的方法可以避免定义first变量
29         for (int i = 1; i <= lenf; i++) {
30             char c = f[i];
31             bf[c] = min(i, bf[c]);
32             ef[c] = i;
33         }
34         for (int i = 1; i <= lens; i++) {
35             char c = s[i];
36             bs[c] = min(i, bs[c]);
37             es[c] = i;
38         }
39 
40         //处理cnt数组
41         for (int i = 0; i <= lenf; i++) 
42             for (int j = 0; j <= lens; j++) {
43                 if (i) {
44                     cnt[i][j] = cnt[i - 1][j];//取走f数组中的一个颜色
45                     char c = f[i];
46                     if (bf[c] == i&&bs[c] > j)cnt[i][j]++;//f中已经出现但是s中没有出现
47                     if (ef[c] == i&&es[c] <= j)cnt[i][j]--;//f中已经结束或者s中已经结束
48                     //上面这个还值得推敲
49                 }
50                 if (j) {
51                     cnt[i][j] = cnt[i][j - 1];
52                     char c = s[j];
53                     if (bs[c] == j&&bf[c] > i)cnt[i][j]++;
54                     if (es[c] == j&&ef[c] <= i)cnt[i][j]--;
55                 }
56             }
57 
58         for (int i = 0; i <= lenf; i++) 
59             for (int j = 0; j <= lens; j++) {
60                 if (!i && !j)continue;
61                 dp[i][j] = inf;
62                 //这点非常重要,+cnt[i][j]其实将所有的字母的情况都考虑进去了
63                 if (i)dp[i][j] = min(dp[i][j], dp[i - 1][j] + cnt[i - 1][j]);
64                 if (j)dp[i][j] = min(dp[i][j], dp[i][j - 1] + cnt[i][j - 1]);
65             }
66         printf("%d
", dp[lenf][lens]);
67     }
68     return 0;
69 }
原文地址:https://www.cnblogs.com/romaLzhih/p/9489797.html