动态规划例题总结
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\n"); 51 else printf("%d\n", 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\n", ++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("\n%d\n", 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\n", 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\n", dp[lenf][lens]); 67 } 68 return 0; 69 }