《挑战程序设计竞赛》课后练习题解集——3.4 熟练掌握动态规划

Posted hs-zlq

tags:

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

《挑战程序设计竞赛》课后练习题解集——3.4 熟练掌握动态规划

状态压缩DP

POJ 2441  有N头牛,M个槽,N,M≤20,每头牛只在指定的pi个槽里进食,不与其他牛共享槽。问有多少种分配方案。

  dp[i][S],当前第i头牛要进食,槽的使用状态为S

技术图片
 1 #include <cstdio>
 2 #include <cstring>
 3 #include <vector>
 4 using namespace std;
 5 
 6 int n, m;
 7 int dp[1 << 21];
 8 vector<int> cow[21];
 9 
10 int next(int k) {
11     int x = k & (-k), y = x + k;
12     return k = ((k & (~y)) / x >> 1) | y;
13 }
14 
15 int main() {
16     int p, x;
17     scanf("%d%d", &n, &m);
18     for (int i = 0; i < n; i++) {
19         scanf("%d", &p);
20         for (int j = 0; j < p; j++) {
21             scanf("%d", &x);
22             x--;
23             cow[i].push_back(x);
24         }
25     }
26     dp[0] = 1;
27     for (int i = 0; i < n; i++) {
28         for (int j = (1 << i + 1) - 1; j < 1 << m; j = next(j)) {
29             for (int k = 0; k < cow[i].size(); k++) {
30                 if (j >> cow[i][k] & 1) {
31                     dp[j] += dp[j & ~(1 << cow[i][k])];
32                 }
33             }
34         }
35     }
36     int res = 0;
37     for (int i = (1 << n) - 1; i < 1 << m; i = next(i)) {
38         res += dp[i];
39     }
40     printf("%d", res);
41 }
View Code

 

POJ 3254  M×N的农场,N, M≤12,农夫要选择若干个肥沃的地方种种子,这些种子不能相邻,问有多少种方案。

  和书上的铺砖问题做法类似,但是要注意此时used数组的含义不是已有种子,而是(i-1, j)有种子,所以(i, j+1)used为true时,(i, j)仍然可以播种;另外初始化的时候,可以全为1或者有意义的情况为1,因为对于(m-1, n-1) dp数组是考虑最后一行的放法,原先的铺砖问题砖一定不会铺到下一行,所有初始化只需dp[0]=1。

技术图片
 1 #include <cstdio>
 2 #include <cstring>
 3 #include <iostream>
 4 using namespace std;
 5 const int mod = 1e8;
 6 
 7 int m, n, cnt, a[13][13];
 8 int dp[2][(1 << 12) + 5];
 9 
10 int main() {
11     scanf("%d %d", &m, &n);
12     for (int i = 0; i < m; i++) {
13         for (int j = 0; j < n; j++) scanf("%d", &a[i][j]);
14     }
15     int *crt = dp[0], *next = dp[1];
16 
17     /*  int last = 0;
18       for (int i = n - 1; i >= 0; i--) {
19           last <<= 1;
20           last += a[m - 1][i] == 0;
21       }*/
22     for (int i = 0; i < 1 << n; i++) {
23         /* if (!(last & i)) */ crt[i] = 1;
24     }
25 
26     for (int i = m - 1; i >= 0; i--) {
27         for (int j = n - 1; j >= 0; j--) {
28             for (int s = 0; s < 1 << n; s++) {
29                 if (a[i][j] == 0 || s >> j & 1)
30                     next[s] = crt[s & ~(1 << j)];
31                 else {
32                     int t = s | 1 << j;
33                     if (j < n - 1) t |= 1 << j + 1;
34                     next[s] = (crt[s] + crt[t]) % mod;
35                 }
36             }
37             swap(crt, next);
38         }
39     }
40     printf("%d
", crt[0]);
41 }
View Code

另外,还可以预处理不相邻的种子的放法,按行DP,枚举上下两行的情况来转移

技术图片
 1 #include <cstdio>
 2 #include <cstring>
 3 #include <iostream>
 4 using namespace std;
 5 const int mod = 1e8;
 6 
 7 int m, n, cnt, a[13][13], bad[13];
 8 int sta[400], dp[2][400];
 9 
10 int main() {
11     scanf("%d%d", &m, &n);
12     for (int i = 0; i < m; i++) {
13         for (int j = 0; j < n; j++) scanf("%d", &a[i][j]);
14         for (int j = n - 1; j >= 0; j--) {
15             bad[i] <<= 1;
16             bad[i] += !a[i][j];
17         }
18     }
19     for (int i = 0; i < 1 << n; i++) {
20         if (!((i << 1) & i)) sta[cnt++] = i;
21     }
22     int *crt = dp[0], *next = dp[1];
23     for (int i = 0; i < cnt; i++) {
24         if (!(sta[i] & bad[0])) crt[i] = 1;
25     }
26     for (int i = 1; i < m; i++) {
27         for (int j = 0; j < cnt; j++) {  //当前行的放置方式
28             if (sta[j] & bad[i]) continue;
29             for (int k = 0; k < cnt; k++) {  //上一行的放置方式
30                 if (sta[j] & sta[k]) continue;
31                 next[j] = (next[j] + crt[k]) % mod;
32             }
33         }
34         swap(crt, next);
35         for (int i = 0; i < cnt; i++) next[i] = 0;
36     }
37     int res = 0;
38     for (int i = 0; i < cnt; i++) res = (res + crt[i]) % mod;
39     printf("%d", res);
40 }
View Code

 

POJ 2836  平面上有N(≤15)个点,要用若干个矩形覆盖这些点,每个矩形至少覆盖到两个点且面积大于0,问这些矩形的面积总和的最小值。

  任何方案都可以分解成若干个这样的矩形,它的对角一定有2个点。枚举N^2这样的矩形作为转移边,以覆盖哪些点为状态,就可以转移了

技术图片
 1 #include <cstdio>
 2 #include <cstring>
 3 #include <iostream>
 4 using namespace std;
 5 const int INF = 0x7f7f7f7f;
 6 
 7 int n, cnt, x[16], y[16];
 8 int area[270], cover[270], dp[1 << 16];
 9 
10 int main() {
11     while (~scanf("%d", &n) && n) {
12         for (int i = 0; i < n; i++) {
13             scanf("%d", &x[i]);
14             scanf("%d", &y[i]);
15         }
16         cnt = 0;
17         memset(cover, 0, sizeof(cover));
18         memset(dp, 127, sizeof(dp));
19         for (int i = 0; i < n; i++) {
20             for (int j = i + 1; j < n; j++) {
21                 int x1 = min(x[i], x[j]), x2 = max(x[i], x[j]),
22                     y1 = min(y[i], y[j]), y2 = max(y[i], y[j]);
23                 area[cnt] = (x1 == x2 ? 1 : x2 - x1) * (y1 == y2 ? 1 : y2 - y1);
24                 for (int k = 0; k < n; k++) {
25                     if (x1 <= x[k] && x[k] <= x2 && y1 <= y[k] && y[k] <= y2) {
26                         cover[cnt] += (1 << k);
27                     }
28                 }
29                 cnt++;
30             }
31         }
32         dp[0] = 0;
33         for (int i = 0; i < 1 << n; i++) {
34             if (dp[i] != INF) {
35                 for (int j = 0; j < cnt; j++) {
36                     dp[i | cover[j]] = min(dp[i | cover[j]], dp[i] + area[j]);
37                 }
38             }
39         }
40         printf("%d
", dp[(1 << n) - 1]);
41     }
42 }
View Code

 

POJ 1795  给出N个串Si, N≤15, Si≤100,问最短且字典序最小的主串,使得这N个串都是它的子串。

  预处理N个串间互为字串的情况,剩下要决定的就是连接顺序。dp[i][S],代表包含的串集合为S,开头是串i,之所以以开头而不是结尾为是为了后面要求字典序最小方便。(我自己的这个版本代码写麻烦了,dp的时候就储存串的连接顺序要好些;而且在以长度回溯串时,如果没有预处理起始串互为字串,只比较前缀其实是错的)

技术图片
 1 #include <cstdio>
 2 #include <cstring>
 3 #include <iostream>
 4 using namespace std;
 5 const int INF = 0x7f7f7f7f;
 6 
 7 int n, cnt, sta[16];
 8 string str[16];
 9 int cost[16][16], dp[16][(1 << 15) + 5];
10 
11 int main() {
12     int T;
13     scanf("%d", &T);
14     for (int I = 1; I <= T; I++) {
15         memset(sta, 0, sizeof(sta));
16         memset(cost, 0, sizeof(cost));
17         memset(dp, 127, sizeof(dp));
18         cnt = 0;
19         scanf("%d", &n);
20         for (int i = 0; i < n; i++) cin >> str[i];
21         for (int i = 0; i < n; i++) {
22             for (int j = i + 1; j < n; j++) {
23                 if (str[i].find(str[j]) != string::npos)
24                     sta[j] = 1;
25                 else if (str[j].find(sta[i]) != string::npos)
26                     sta[i] = 1;
27             }
28         }
29         for (int i = 0; i < n; i++)
30             if (!sta[i]) str[cnt++] = str[i];
31         for (int i = 0; i < cnt; i++) {
32             for (int j = 0; j < cnt; j++) {
33                 if (i != j) {
34                     int len = min(str[i].size(), str[j].size());
35                     for (int k = 0; k <= len; k++) {
36                         if (str[i].substr(str[i].size() - k) ==
37                             str[j].substr(0, k)) {
38                             cost[i][j] = str[i].size() - k;
39                             //在j前面接i要花cost[i][j]的代价
40                         }
41                     }
42                 }
43             }
44         }
45         for (int i = 0; i < cnt; i++) dp[i][1 << i] = str[i].size();
46         for (int i = 1; i < 1 << cnt; i++) {
47             for (int j = 0; j < cnt; j++) {
48                 if (dp[j][i] != INF) {
49                     for (int k = 0; k < cnt; k++) {
50                         if (!(i >> k & 1)) {
51                             dp[k][i | (1 << k)] =
52                                 min(dp[k][i | (1 << k)], cost[k][j] + dp[j][i]);
53                         }
54                     }
55                 }
56             }
57         }
58         int id = 0, sz = (1 << cnt) - 1;
59         for (int i = 1; i < cnt; i++) {
60             if (dp[i][sz] < dp[id][sz] ||
61                 dp[i][sz] == dp[id][sz] && str[i] < str[id]) {
62                 id = i;
63             }
64         }
65         string ans = str[id];
66         int S = sz;
67         for (int i = 1; i < cnt; i++) {
68             int next_id = -1;
69             string tmp = "";
70             for (int j = 0; j < cnt; j++) {
71                 if (id != j && (S >> j & 1) &&
72                     dp[id][S] == dp[j][S & ~(1 << id)] + cost[id][j]) {
73                     string t = str[j].substr(str[id].size() - cost[id][j]);
74                     if (next_id == -1 || t < tmp) {
75                         tmp = t;
76                         next_id = j;
77                     }
78                 }
79             }
80             ans += tmp;
81             S &= ~(1 << id);
82             id = next_id;
83         }
84         printf("Scenario #%d:
", I);
85         cout << ans << "

";
86     }
87 }
View Code

 

POJ 3411  给出N个点,M条边,N, M≤10,每条边有ai, bi, ci, Pi, Ri,Pi≤Ri,表示从点ai到bi时,若已经过ci,代价为Pi,否则为Ri。问从点1到点N的最小代价

  和旅行商问题类似,dp[i][S]表示当前经过的点集合为S,在点 i 的最小代价。但是此时状态之间不是拓扑序,可能出现回到已经经过过的点的情况,所以要用最段路算法。

技术图片
 1 #include <cstdio>
 2 #include <cstring>
 3 #include <iostream>
 4 #include <queue>
 5 #include <vector>
 6 using namespace std;
 7 #define P pair<int, int>
 8 const int INF = 0x7f7f7f7f;
 9 
10 int n, m;
11 int dp[11][(1 << 10) + 5];
12 
13 struct edge {
14     int to, c, p, r;
15 };
16 vector<edge> G[11];
17 
18 struct node {
19     int cost, u, S;
20     bool operator<(const node& o) const { return cost < o.cost; }
21 };
22 priority_queue<node> que;
23 
24 int main() {
25     int a, b, c, p, r;
26     scanf("%d %d", &n, &m);
27     for (int i = 0; i < m; i++) {
28         scanf("%d %d %d %d %d", &a, &b, &c, &p, &r);
29         a--, b--, c--;
30         edge x = {b, c, p, r};
31         G[a].push_back(x);
32     }
33     memset(dp, 127, sizeof(dp));
34     dp[0][1] = 0;
35     node o = {0, 0, 1};
36     que.push(o);
37     while (!que.empty()) {
38         node p = que.top();
39         que.pop();
40         int u = p.u, rou = p.S;
41         for (int i = 0; i < G[u].size(); i++) {
42             edge es = G[u][i];
43             int v = es.to, cost;
44             if (rou >> es.c & 1)
45                 cost = es.p;
46             else
47                 cost = es.r;
48             if (dp[v][rou | (1 << v)] > dp[u][rou] + cost) {
49                 dp[v][rou | (1 << v)] = dp[u][rou] + cost;
50                 o = {dp[v][rou | (1 << v)], v, rou | 1 << v};
51                 que.push(o);
52             }
53         }
54     }
55     int res = INF;
56     for (int i = 0; i < 1 << n; i++) res = min(res, dp[n - 1][i]);
57     if (res != INF)
58         printf("%d
", res);
59     else
60         printf("impossible
");
61 }
View Code

另外不知道为什么用队列代替优先队列也能0ms过。dij用队列代替优先队列的算法复杂度有待分析

技术图片
 1 #include <cstdio>
 2 #include <cstring>
 3 #include <iostream>
 4 #include <queue>
 5 #include <vector>
 6 using namespace std;
 7 #define P pair<int, int>
 8 const int INF = 0x7f7f7f7f;
 9 
10 int n, m;
11 int dp[11][(1 << 10) + 5];
12 
13 struct edge {
14     int to, c, p, r;
15 };
16 vector<edge> G[11];
17 
18 queue<P> que;
19 
20 int main() {
21     int a, b, c, p, r;
22     scanf("%d %d", &n, &m);
23     for (int i = 0; i < m; i++) {
24         scanf("%d %d %d %d %d", &a, &b, &c, &p, &r);
25         a--, b--, c--;
26         edge x = {b, c, p, r};
27         G[a].push_back(x);
28     }
29     memset(dp, 127, sizeof(dp));
30     dp[0][1] = 0;
31     que.push(P(0, 1));
32     while (!que.empty()) {
33         P p = que.front();
34         que.pop();
35         int u = p.first, rou = p.second;
36         for (int i = 0; i < G[u].size(); i++) {
37             edge es = G[u][i];
38             int v = es.to, cost;
39             if (rou >> es.c & 1)
40                 cost = es.p;
41             else
42                 cost = es.r;
43             if (dp[v][rou | (1 << v)] > dp[u][rou] + cost) {
44                 dp[v][rou | (1 << v)] = dp[u][rou] + cost;
45                 que.push(P(v, rou | (1 << v)));
46             }
47         }
48     }
49     int res = INF;
50     for (int i = 0; i < 1 << n; i++) res = min(res, dp[n - 1][i]);
51     if (res != INF)
52         printf("%d
", res);
53     else
54         printf("impossible
");
55 }
View Code

 

 矩阵的幂

POJ 3420  问用1×2的矩阵填充4×N的矩阵有多少种方法。

  设题述答案为An。考虑这样一类的填充方案,它在前N-1列的空隙中,都至少有一个横放的矩形穿过了这个空隙,设这样的方案有Bn种。我们有B1=1, B2 = 4, Bi = 2 (i为大于2的奇数),Bi = 3(i为大于2的偶数)。对于一般的填充方案,考虑从左到右(右到左是一样)第一列不满足有横放的矩阵穿过空隙的情况(也可能不存在,属于Bi的范畴),然后以此为界限就可以把整个矩形视作两部分,让左右方案数相乘。得到递推关系 An = B1 × An-1 + B2 × An-2 + ... + Bn = An-1 + 4An-2 + 2An-3 + 3An-4 + ...。我们发现后面系数是以“2 3 2 3”循环的,利用这个可以消去后面的许多项,最后得到An = An-1 + 5×An-2 + An-3 - An-4。

技术图片
 1 #include <cstdio>
 2 #include <iostream>
 3 #include <vector>
 4 using namespace std;
 5 #define ll long long
 6 typedef vector<long long> vec;
 7 typedef vector<vec> mat;
 8 
 9 int n, m;
10 
11 mat mul(mat A, mat B) {
12     mat C(4, vec(4, 0));
13     for (int i = 0; i < 4; i++) {
14         for (int j = 0; j < 4; j++) {
15             for (int k = 0; k < 4; k++) {
16                 C[i][j] = ((C[i][j] + A[i][k] * B[k][j]) % m + m) % m;
17             }
18         }
19     }
20     return C;
21 }
22 
23 mat ksm(mat A, int x) {
24     mat B(4, vec(4, 0));
25     for (int i = 0; i < 4; i++) B[i][i] = 1;
26     while (x) {
27         if (x & 1) B = mul(B, A);
28         A = mul(A, A);
29         x >>= 1;
30     }
31     return B;
32 }
33 
34 void solve() {
35     mat A(4, vec(4));
36     A[0][0] = 1, A[0][1] = 5, A[0][2] = 1, A[0][3] = -1;
37     A[1][0] = A[2][1] = A[3][2] = 1;
38     A = ksm(A, n - 1);
39     printf("%lld
", (A[3][0] * 36 + A[3][1] * 11 + A[3][2] * 5 + A[3][3]) % m);
40 }
41 
42 int main() {
43     while (~scanf("%d%d", &n, &m), n && m) {
44         solve();
45     }
46 }
View Code

 

 POJ 3735  有n只小猫,要依次执行k个操作并重复m次,n≤100, k≤100, ≤1e9,操作分别有:1. 使第i只小猫获得一个坚果;2. 使第i只小猫吃掉它的所有坚果;3. 交换第i只小猫与第j只小猫的坚果。问最后每只小猫拥有坚果的情况。

  小猫的坚果数和常数1写作列向量,3种操作都对应到矩阵运算即可。这题的矩阵相乘需要判0优化,否则会T。

技术图片
 1 #include <cstdio>
 2 #include <iostream>
 3 #include <vector>
 4 using namespace std;
 5 #define ll long long
 6 typedef vector<ll> vec;
 7 typedef vector<vec> mat;
 8 
 9 int n, m, k;
10 
11 mat mul(mat &A, mat &B) {
12     mat C(A.size(), vec(B[0].size()));
13     for (int i = 0; i < A.size(); i++) {
14         for (int j = 0; j < A[0].size(); j++) {
15             if (A[i][j])
16                 for (int k = 0; k < B[0].size(); k++) {
17                     C[i][k] += A[i][j] * B[j][k];
18                 }
19         }
20     }
21     return C;
22 }
23 
24 mat ksm(mat A, int x) {
25     mat B(A.size(), vec(A.size()));
26     for (int i = 0; i < A.size(); i++) B[i][i] = 1;
27     while (x) {
28         if (x & 1) B = mul(B, A);
29         A = mul(A, A);
30         x >>= 1;
31     }
32     return B;
33 }
34 
35 int main() {
36     char op;
37     int x, y;
38     while (~scanf("%d%d%d", &n, &m, &k), n || m || k) {
39         mat a(n + 1, vec(n + 1));
40         for (int i = 0; i < n + 1; i++) a[i][i] = 1;
41         for (int i = 0; i < k; i++) {
42             getchar();
43             scanf("%c", &op);
44             if (op == g) {
45                 scanf("%d", &x);
46                 for (int i = 0; i < n + 1; i++) a[x - 1][i] += a[n][i];
47             } else if (op == e) {
48                 scanf("%d", &x);
49                 for (int i = 0; i < n + 1; i++) a[x - 1][i] = 0;
50             } else {
51                 scanf("%d%d", &x, &y);
52                 for (int i = 0; i < n + 1; i++) swap(a[x - 1][i], a[y - 1][i]);
53             }
54         }
55         a = ksm(a, m);
56         for (int i = 0; i < n; i++)
57             printf("%lld%c", a[i][n], i == n - 1 ? 
 :  );
58     }
59 }
View Code

 

 利用数据结构高效求解

 POJ 3171  有N只牛,每只牛可以在[T1, T2]的时间里工作,需要支付它S薪水。现需要[M, E]的时间区间里每一时刻都有牛在工作,问最少支付的薪水是多少

  dp[i]表示到第i时刻都有牛工作最少需要支付的薪水是多少,线段树维护。

技术图片
 1 #include <algorithm>
 2 #include <cstdio>
 3 #include <iostream>
 4 using namespace std;
 5 #define ll long long
 6 const int N = 1e4 + 5;
 7 const int M = 86399 + 5;
 8 const ll INF = 1e10;
 9 
10 int n, m, e;
11 
12 ll min(ll a, ll b) { return a < b ? a : b; }
13 
14 struct sec {
15     int l, r, s;
16 } a[N];
17 
18 bool operator<(sec x, sec y) { return x.r < y.r; }
19 
20 struct node {
21     int l, r, f;
22     ll Min;
23 } tree[M * 4];
24 
25 void build(int k, int lch, int rch) {
26     tree[k] = {lch, rch, 0, INF};
27     if (lch == rch) return;
28     int mid = (lch + rch) / 2;
29     build(k * 2, lch, mid);
30     build(k * 2 + 1, mid + 1, rch);
31 }
32 
33 void down(int k) {
34     if (tree[k * 2].Min > tree[k].Min) {
35         tree[k * 2].Min = tree[k].Min;
36         tree[k * 2].f = 1;
37     }
38     if (tree[k * 2 + 1].Min > tree[k].Min) {
39         tree[k * 2 + 1].Min = tree[k].Min;
40         tree[k * 2 + 1].f = 1;
41     }
42     tree[k].f = 0;
43 }
44 
45 ll ask(int k, int x) {
46     if (tree[k].l == tree[k].r) return tree[k].Min;
47     if (tree[k].f) down(k);
48     int mid = (tree[k].l + tree[k].r) / 2;
49     if (x <= mid) return ask(k * 2, x);
50     return ask(k * 2 + 1, x);
51 }
52 
53 void update(int k, int lch, int rch, ll num) {
54     if (lch <= tree[k].l && tree[k].r <= rch) {
55         tree[k].Min = min(tree[k].Min, num);
56         tree[k].f = 1;
57         return;
58     }
59     if (tree[k].f) down(k);
60     int mid = (tree[k].l + tree[k].r) / 2;
61     if (lch <= mid) update(k * 2, lch, rch, num);
62     if (rch > mid) update(k * 2 + 1, lch, rch, num);
63 }
64 
65 int main() {
66     scanf("%d %d %d", &n, &m, &e);
67     e -= (m - 1);
68     for (int i = 0; i < n; i++) {
69         scanf("%d %d %d", &a[i].l, &a[i].r, &a[i].s);
70         a[i].l -= (m - 1);
71         a[i].r -= (m - 1);
72     }
73     sort(a, a + n);
74     build(1, 1, e);
75     for (int i = 0; i < n; i++) {
76         if (a[i].l == 1) {
77             update(1, a[i].l, a[i].r, a[i].s);
78         } else {
79             update(1, a[i].l, a[i].r, ask(1, a[i].l - 1) + a[i].s);
80         }
81     }
82     ll res = ask(1, e);
83     if (res == INF)
84         printf("-1
");
85     else
86         printf("%lld
", res);
87 }
View Code

 

END

以上是关于《挑战程序设计竞赛》课后练习题解集——3.4 熟练掌握动态规划的主要内容,如果未能解决你的问题,请参考以下文章

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

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

AOJ 0033 Ball 题解 《挑战程序设计竞赛》

[转] AOJ 0525 Osenbei《挑战程序设计竞赛(第2版)》练习题答案

POJ 1862 Stripies 题解 《挑战程序设计竞赛》

WUT/武汉理工PTA-2021年春-MOOC-《C编程方法学》编程练习题解集