《挑战程序设计竞赛》课后练习题解集——2.3 记录结果再利用的“动态规划”

Posted hs-zlq

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《挑战程序设计竞赛》课后练习题解集——2.3 记录结果再利用的“动态规划”相关的知识,希望对你有一定的参考价值。

2.3 记录结果再利用的“动态规划”

基础的动态规划算法

 

POJ 3176  从三角形顶端走到底边使经过的数字和最大

从下往上逆推答案

技术图片
 1 #include <cmath>
 2 #include <iostream>
 3 using namespace std;
 4 
 5 int row[355][355];
 6 
 7 int main() {
 8     int n;
 9     scanf("%d", &n);
10     for (int i = 1; i <= n; i++) {
11         for (int j = 1; j <= i; j++) scanf("%d", &row[i][j]);
12     }
13     for (int i = n - 1; i >= 1; i--) {
14         for (int j = 1; j <= i; j++)
15             row[i][j] += max(row[i + 1][j], row[i + 1][j + 1]);
16     }
17     printf("%d
", row[1][1]);
18 }
View Code

 

POJ 2229  问一个数拆成2的幂次之和有多少种拆法

考虑当前数字的组成方案中可能有几个"1",把这些1去掉之后,剩下的数(都是偶数)除以二就一一对应于 剩下的数 的和 对应的答案

另解:考虑当前数字的组成方案中是否有1,得出递推关系式 dp[i] = dp[i - 1] + dp[i >> 1](i为偶数)

技术图片
 1 #include <cstdio>
 2 using namespace std;
 3 
 4 const int mod = 1e9;
 5 const int maxn = 1e6 + 5;
 6 
 7 int a[maxn], s[maxn], x;
 8 
 9 int main () {
10     a[0] = s[0] = 1;
11     for(int i = 1; i <= 1000000; i++) {
12         a[i] = s[i / 2];
13         s[i] = (s[i - 1] + a[i]) % mod;
14     }
15     scanf("%d", &x);
16     printf("%d
", a[x]);
17 }
View Code

 

POJ 2385 接苹果问题

关键:dp[i][j] 在第i分钟,cow移动了j次

技术图片
 1 #include <cstdio>
 2 using namespace std;
 3 
 4 int a[1005], even[1005], n, w;
 5 int dp[1005][35];
 6 
 7 int max(int a, int b) {
 8     return a > b ? a : b;
 9 }
10 
11 int main () {
12     scanf("%d%d", &n, &w);
13     for(int i = 1; i <= n; i++) {
14         scanf("%d", &a[i]);
15         even[i] = even[i - 1] + (a[i] == 1);
16     } 
17     for(int i = 1; i<= n; i++) {
18         dp[i][0] = even[i];
19         for(int j = 1; j <= w; j++) {
20             for(int k = 0; k <= i; k++) {
21                 if(j & 1) dp[i][j] = max(dp[k][j - 1] + (i - k) - (even[i] - even[k]), dp[i][j]);
22                 else dp[i][j] = max(dp[k][j - 1] + even[i] - even[k], dp[i][j]);            
23             }
24         }
25     }
26     printf("%d
", dp[n][w]);
27 }
View Code
技术图片
 1 #include <cstdio>
 2 using namespace std;
 3 
 4 int a[1005], n, w, ans;
 5 int dp[1005][35];
 6 
 7 int max(int a, int b) {
 8     return a > b ? a : b;
 9 }
10 
11 int main () {
12     scanf("%d%d", &n, &w);
13     for(int i = 1; i <= n; i++) {
14         scanf("%d", &a[i]);
15     } 
16     for(int i = 1; i <= n; i++) {
17         dp[i][0] = dp[i - 1][0];
18         for(int j = 1; j <= w; j++) {
19             dp[i][j] = max(dp[i - 1][j - 1], dp[i - 1][j]);
20         }
21         if (a[i] & 1) for(int j = 0; j <= w; j+=2) dp[i][j]++;
22         else for(int j = 1; j <= w; j+=2) dp[i][j]++;
23     }
24     for(int i = 0; i <= w; i++) ans = max(ans, dp[n][i]);
25     printf("%d
", ans); 
26 }
View Code

 

POJ 3616 给出N个区间及其权值,取不重复的若干区间使权值和最大

(这一题结束时刻是可以与起始时刻重合的)

考虑从时间上的转移或者从具体某一区间转移过来

技术图片
 1 #include <cstdio>
 2 #include <iostream>
 3 #include <algorithm>
 4 using namespace std;
 5 #define ll long long
 6 
 7 const int maxn = 1e6 + 5;
 8 
 9 int n, m, r;
10 ll dp[maxn];
11 struct pii {
12     int s, e, val;
13     bool operator < (const pii &o) const { return e < o.e; }
14 } p[maxn];
15 
16 ll max(ll a, ll b) {
17     return a > b ? a : b;
18 }
19 
20 int main () {
21     scanf("%d %d %d", &n, &m, &r);
22     for(int i = 0; i < m; i++) {
23         scanf("%d %d %d", &p[i].s, &p[i].e, &p[i].val);
24         p[i].e += r;
25     } 
26     sort(p ,p + n);
27     for(int i = 0, t = 0; t <= n + r; t++) {
28         dp[t] = dp[t - 1];
29         while(i < n && p[i].e == t) {
30             dp[t] = max(dp[p[i].s] + p[i].val, dp[t]);
31             i++;
32         }
33     }
34     printf("%lld
", dp[n + r]);
35 }
View Code
技术图片
 1 #include <algorithm>
 2 #include <cmath>
 3 #include <iostream>
 4 #include <cstdio>
 5 using namespace std;
 6 #define ll long long
 7 
 8 ll dp[1005];
 9 
10 struct inf {
11     int s, e, v;
12 } inter[1001];
13 
14 bool cmp(const inf &x, const inf &y) { return x.e < y.e; }
15 
16 ll max(ll a, ll b) {
17     return a > b ? a : b;
18 }
19 
20 int main() {
21     int n, m, r;
22     scanf("%d %d %d", &n, &m, &r);
23     for (int i = 0; i < m; i++) {
24         scanf("%d %d %d", &inter[i].s, &inter[i].e, &inter[i].v);
25         inter[i].e += r;
26     }
27     sort(inter, inter + m, cmp);
28     dp[0] = inter[0].v;
29     for (int i = 1; i < m; i++) {
30         inf t = {0, inter[i].s, 0};
31         inf *ite = upper_bound(inter, inter + i, t, cmp);
32         if (ite == inter)
33             dp[i] = max(dp[i - 1], (ll)inter[i].v);
34         else
35             dp[i] = max(dp[i - 1], dp[ite - inter - 1] + inter[i].v);
36     }
37     printf("%lld
", dp[m - 1]);
38 }
View Code

 

POJ 3280  给出删除和添加某字符的代价,对于给定字符串,求经过操作后的字符串是回文串最小代价

添加和删除是对称的操作,取其中的较小值即可。dp[i][j]代表从位置i到j的字串变成回文串需要的最小代价

技术图片
 1 #include <algorithm>
 2 #include <cmath>
 3 #include <iostream>
 4 using namespace std;
 5 
 6 int value[30], dp[2005][2005];
 7 int n, m, x, y;
 8 string str;
 9 char s;
10 
11 int main() {
12     ios::sync_with_stdio(false);
13     cin >> n >> m >> str;
14     for (int i = 0; i < n; i++) {
15         cin >> s >> x >> y;
16         value[s - a] = min(x, y);
17     }
18     for (int i = m - 1; i >= 0; i--) {
19         for (int j = i + 1; j < m; j++) {
20             if (str[i] == str[j]) {
21                 dp[i][j] = dp[i + 1][j - 1];
22             } else {
23                 dp[i][j] = min(dp[i + 1][j] + value[str[i] - a],
24                                dp[i][j - 1] + value[str[j] - a]);
25             }
26         }
27     }
28     cout << dp[0][m - 1];
29 }
View Code

 

优化递推关系式

 

POJ 1742  多重部分和问题

技术图片
 1 #include <cstdio>
 2 #include <cstring>
 3 #include <iostream>
 4 using namespace std;
 5 
 6 int dp[100005], res;
 7 
 8 int a[105], c[105], n, m;
 9 
10 int main() {
11     while (scanf("%d %d", &n, &m) && n) {
12         memset(dp, -1, sizeof(dp));
13         dp[0] = res = 0;
14         for (int i = 0; i < n; i++) scanf("%d", &a[i]);
15         for (int i = 0; i < n; i++) scanf("%d", &c[i]);
16         for (int i = 0; i < n; i++) {
17             for (int j = 0; j < a[i]; j++)
18                 if (dp[j] >= 0) dp[j] = c[i];
19             for (int j = a[i]; j <= m; j++) {
20                 if (dp[j] == -1) {
21                     if (dp[j - a[i]] <= 0)
22                         dp[j] = -1;
23                     else
24                         dp[j] = dp[j - a[i]] - 1;
25                 } else {
26                     dp[j] = c[i];
27                 }
28             }
29         }
30         for (int i = 1; i <= m; i++) res += (dp[i] >= 0);
31         printf("%d
", res);
32     }
33 }
View Code

 

POJ 3046  多重集组合数 

技术图片
 1 #include <cstdio>
 2 #include <cstring>
 3 #include <iostream>
 4 using namespace std;
 5 
 6 const int mod = 1e6;
 7 int res, dp[2][100005];
 8 int t, a, s, b, x;
 9 int c[1005];
10 
11 int main() {
12     scanf("%d%d%d%d", &t, &a, &s, &b);
13     for (int i = 0; i < a; i++) {
14         scanf("%d", &x);
15         c[x]++;
16     }
17     dp[0][0] = dp[1][0] = 1;
18     for (int i = 1; i <= t; i++) {
19         for (int j = 1; j <= c[i]; j++)
20             dp[i & 1][j] = (dp[i & 1][j - 1] + dp[1 - i & 1][j]) % mod;
21         for (int j = c[i] + 1; j <= b; j++)
22             dp[i & 1][j] = (dp[i & 1][j - 1] + dp[1 - i & 1][j] -
23                             dp[1 - i & 1][j - c[i] - 1] + mod) %
24                            mod;
25     }
26     for (int i = s; i <= b; i++) res = (res + dp[t & 1][i]) % mod;
27     printf("%d
", res);
28 }
View Code

 

POJ 3181  无数量限制的多重部分和+高精度

dp[i]不再表示当前物品用剩多少,而是方案数

技术图片
 1 #include <algorithm>
 2 #include <cstring>
 3 #include <iostream>
 4 #define ll long long
 5 using namespace std;
 6 
 7 string dp[2][1005];
 8 
 9 string add(string a, string b) {
10     if (a.size() < b.size()) swap(a, b);
11     int f = 0;
12     for (int i = a.size() - 1, j = b.size() - 1; j >= 0; i--, j--) {
13         a[i] += b[j] - 0 + f;
14         if (a[i] > 9) {
15             a[i] -= 10;
16             f = 1;
17         } else
18             f = 0;
19     }
20     for (int i = a.size() - b.size() - 1; i >= 0 && f; i--) {
21         f = 0;
22         a[i]++;
23         if (a[i] > 9) {
24             a[i] -= 10;
25             f = 1;
26         }
27     }
28     if (f) a = "1" + a;
29     return a;
30 }
31 
32 int main() {
33        int n, k;
34     cin >> n >> k;
35     dp[0][0] = "1";
36     for (int i = 1; i <= k; i++) {
37         fill(dp[1], dp[1] + 1001, "0");
38         for (int j = 0; j < i; j++) dp[1][j] = dp[0][j];
39         for (int j = i; j <= n; j++) dp[1][j] = add(dp[1][j - i], dp[0][j]);
40         swap(dp[0], dp[1]);
41     }
42     cout << dp[0][n];
43 }
View Code

 

需稍加思考的题目

 

POJ 1065  给出N个矩形,长度递增且宽度递增的一组矩形可以划在一起,求最小组数

贪心

技术图片
 1 #include <algorithm>
 2 #include <cstring>
 3 #include <iostream>
 4 using namespace std;
 5 
 6 struct inf {
 7     int l, w;
 8 } stick[5001];
 9 
10 bool cmp(inf x, inf y) { return x.l < y.l; }
11 
12 int vis[5005];
13 
14 int main() {
15     int t, n, x, y;
16     cin >> t;
17     while (t--) {
18         cin >> n;
19         for (int i = 0; i < n; i++) {
20             cin >> stick[i].l >> stick[i].w;
21         }
22         sort(stick, stick + n, cmp);
23         memset(vis, 0, sizeof(vis));
24         int sum = -1, t;
25         do {
26             t = 0;
27             for (int i = 0; i < n; i++) {
28                 if (!vis[i] && (t == 0 || t <= stick[i].w)) {
29                     t = stick[i].w;
30                     vis[i] = 1;
31                 }
32             }
33             sum++;
34         } while (t);
35         cout << sum << "
";
36     }
37 }
View Code

 

POJ 1631  给出n组连线(二分图的形状),求其中最大的互不交叉的子集

以左边为基准时,子集对应的右边的序号应该是递增的。所以问题转化为最大上升子序列

技术图片
 1 #include <algorithm>
 2 #include <cstdio>
 3 #include <iostream>
 4 using namespace std;
 5 
 6 int c[40005];
 7 int dp[40005];
 8 
 9 int main() {
10     int n, a;
11     scanf("%d", &n);
12     while (n--) {
13         scanf("%d", &a);
14         for (int i = 0; i < a; i++) scanf("%d", c[i]);
15         fill(dp, dp + a, 0x7f7f7f7f);
16         for (int i = 0; i < a; i++) {
17             *lower_bound(dp, dp + a, c[i]) = c[i];
18         }
19        printf("%d
", lower_bound(dp, dp + a, 0x7f7f7f7f) - dp);
20     }
21 }
View Code

 

POJ 3666  给出一个序列,可以对某一个数字+1/-1的调整,最终使序列不增/不减。求最小代价(测试数据不严格,只要处理不减的情况就可以过)

好题目。dp[i][j]的意义是 使前i个数都降到比第j大的数小于或者等于 并且这个字串是不减的 的 代价

状态转移时,考虑前n个数,要么全比第j-1小,要么至少有一个数(第i个)变成第j大;前者即dp[i][j-1],后者是dp[i-1][j]+es[j]-a[i]

技术图片
 1 #include <algorithm>
 2 #include <cstdio>
 3 #include <iostream>
 4 using namespace std;
 5 
 6 #define ll long long
 7 
 8 ll dp[2005];
 9 
10 int n, a[2005], es[2005];
11 
12 ll min(ll a, ll b) { return a < b ? a : b; }
13 
14 int main() {
15     scanf("%d", &n);
16     for (int i = 0; i < n; i++) {
17         scanf("%d", &a[i]);
18         es[i] = a[i];
19     }
20     sort(es, es + n);
21     for (int i = 0; i < n; i++) {
22         for (int j = 0; j < n; j++) {
23             if (a[i] >= es[j])
24                 dp[j] += a[i] - es[j];
25             else
26                 dp[j] = min(dp[j - 1], dp[j] + es[j] - a[i]);
27         }
28     }
29     printf("%lld
", dp[n - 1]);
30 }
View Code

 

POJ 2392  给出N种石头的高度,数量,最高能出现的高度。要求能垒成的最高高度 

好题目。带限制的多重部分和

技术图片
 1 #include <algorithm>
 2 #include <cstdio>
 3 #include <cstdlib>
 4 #include <cstring>
 5 #include <iostream>
 6 using namespace std;
 7 
 8 struct node {
 9     int h, a, c;
10     bool operator<(const node &o) const { return a < o.a; }
11 } s[405];
12 
13 int dp[40005], k;
14 
15 int main() {
16     scanf("%d", &k);
17     for (int i = 0; i < k; i++) scanf("%d %d %d", &s[i].h, &s[i].a, &s[i].c);
18     sort(s, s + k);
19     memset(dp, -1, sizeof(dp));
20     dp[0] = 0;
21     for (int i = 0; i < k; i++) {
22         for (int j = 0; j < s[i].h; j++)
23             if (dp[j] >= 0) dp[j] = s[i].c;
24         for (int j = s[i].h; j <= s[i].a; j++) {
25             if (dp[j] >= 0)
26                 dp[j] = s[i].c;
27             else if (dp[j - s[i].h] > 0)
28                 dp[j] = dp[j - s[i].h] - 1;
29             else
30                 dp[j] = -1;
31         }
32     }
33     for (int i = s[k - 1].a; i >= 0; i--)
34         if (dp[i] >= 0) {
35             printf("%d
", i);
36             exit(0);
37         }
38 }
View Code

 

 POJ 2184  N个元素,各有两个关键字;选取若干个使得每个关键字和非负且使总和尽可能大

有负值的背包问题(对两个关键字和DP时 常数是 对第二关键字DP 的2倍,会T)(因为OJ比较老,其实问题不大)

技术图片
 1 #include <algorithm>
 2 #include <cmath>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <iostream>
 6 using namespace std;
 7 
 8 const int shift = 1e5;
 9 int dp[2][200005];
10 
11 int n, ts, tf, res;
12 
13 int main() {
14     memset(dp[0], 128, sizeof(dp[0]));
15     memset(dp[1], 128, sizeof(dp[1]));
16 
17     dp[0][shift] = 0;
18 
19     scanf("%d", &n);
20 
21     for (int i = 0; i < n; i++) {
22         scanf("%d %d", &ts, &tf);
23         if (ts <= 0 && tf <= 0) continue;
24         for (int j = max(0, ts); j <= (int)2e5 + min(0, ts); j++) {
25             dp[1][j] = max(dp[0][j], dp[0][j - ts] + tf);
26         }
27         for (int j = 0; j < max(0, ts); j++) dp[1][j] = dp[0][j];
28         for (int j = (int)2e5 + min(0, ts) + 1; j <= (int)2e5; j++)
29             dp[1][j] = dp[0][j];
30         swap(dp[0], dp[1]);
31     }
32     for (int i = shift; i <= (int)2e5; i++) {
33         if (dp[0][i] >= 0) res = max(res, i - shift + dp[0][i]);
34     }
35     printf("%d
", res);
36 }
View Code

 

END

以上是关于《挑战程序设计竞赛》课后练习题解集——2.3 记录结果再利用的“动态规划”的主要内容,如果未能解决你的问题,请参考以下文章

《挑战程序设计竞赛》课后练习题解集——2.5 它们其实都是“图”

《挑战程序设计竞赛》课后练习题解集——3.2 常用技巧精选

挑战程序设计竞赛2.3:Wooden Sticks POJ - 1065

挑战程序设计竞赛2.3习题:Cheapest Palindrome POJ - 3280

挑战程序设计竞赛2.3习题:Cow Exhibition POJ - 2184

挑战程序设计竞赛2.3习题:Making the Grade POJ - 3666