算法競賽入門經典(第二版)-劉汝佳-第九章 動態規劃初步 習題(14/23)

說明

本文是我對第9章23道習題的練習總結,建議配合紫書——《算法競賽入門經典(第2版)》閱讀本文。
另外爲了方便做題,我在VOJ上開了一個contest,歡迎一起在上面做:
第九章習題contest(1)
第九章習題contest(2)
如果想直接看某道題,請點開目錄後點開相應的題目!!!

習題

習9-1 UVA 10285 最長的滑雪路徑

題意
在一個R*C(R,C≤100)的整數矩陣上找一條高度嚴格遞減的最長路。起點任意,但每
次只能沿着上下左右4個方向之一走一格,並且不能走出矩陣外。如圖9-29所示,最長路就
是按照高度25, 24, 23,…, 2, 1這樣走,長度爲25。矩陣中的數均爲0~100。

思路
dp[i][j]表示在i,j位置能夠得到的最長路徑。逐次DP即可。
代碼

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

#define LOOP1(i, a, b) for (int i = (a); i <= (b); i++)

const int N = 101;
const int INF = 0x3f3f3f3f;

char name[100];
int n, r, c, A[N][N];
int dp[N][N];
int t[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

struct MAT {
    int v;
    int x;
    int y;
} mat[N*N];

bool operator<(const MAT &a, const MAT &b)
{
    return a.v > b.v;
}

int main(void)
{
#ifdef DEBUG
    freopen("datain", "r", stdin);
#endif

    int kase = 0;
    scanf("%d", &kase);
    LOOP1(i, 0, kase-1) {
        scanf("%s %d %d", name, &r, &c);
        LOOP1(i, 0, r-1) {
            LOOP1(j, 0, c-1) {
                MAT &tmp = mat[i*c+j];
                scanf("%d", &tmp.v);
                A[i][j] = tmp.v;
                tmp.x = i;
                tmp.y = j;
                dp[i][j] = 1;
            }
        }
        sort(mat, mat + r*c);

        LOOP1(i, 0, r*c-1) {
            LOOP1(j, 0, 3) {
                int &x = mat[i].x;
                int &y = mat[i].y;
                int nx = x + t[j][0];
                int ny = y + t[j][1];
                if (nx < 0 || nx > r-1 || ny < 0 || ny > c-1) continue;
                if (A[nx][ny] < A[x][y] && dp[nx][ny] < dp[x][y] + 1)
                    dp[nx][ny] = dp[x][y] + 1;
            }
        }

        int ans = 0;
        LOOP1(i, 0, r-1) {
            LOOP1(j, 0, c-1) {
                ans = max(ans, dp[i][j]);
            }
        }

        printf("%s: %d\n", name, ans);
    }

    return 0;
}

習9-2 UVA 10118 免費糖果

題意
桌上有4堆糖果,每堆有N(N≤40)顆。佳佳有一個最多可以裝5顆糖的小籃子。他每次選擇一堆糖果,把最頂上的一顆拿到籃子裏。如果籃子裏有兩顆顏色相同的糖果,佳佳就把它們從籃子裏拿出來放到自己的口袋裏。如果籃子滿了而裏面又沒有相同顏色的糖果,遊戲結束,口袋裏的糖果就歸他了。當然,如果佳佳足夠聰明,他有可能把堆裏的所有糖果都拿走。爲了拿到儘量多的糖果,佳佳該怎麼做呢?

思路
dp[i][j][k][r]表示已經從4堆糖果中分別取了i、j、k、r個時能夠獲取的最多糖果,另外注意到此時籃子中的糖果狀態是唯一確定的。

代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
#include <set>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int MAXN = 41;

int n;
int dp[MAXN][MAXN][MAXN][MAXN];
set<int> s[MAXN][MAXN][MAXN][MAXN]; //表示該狀態下籃子中的糖果(最多5顆)
int mat[4][MAXN];

int solve(int i, int j, int k, int r) {
	int &res = dp[i][j][k][r];
	if (res != -1) //表示訪問過
		return res;
	set<int> &ss = s[i][j][k][r];
	if (i) {
		int res1 = solve(i - 1, j, k, r);
		set<int> &ss1 = s[i - 1][j][k][r];
		if (ss1.size() < 5) {
			if (ss1.size() - ss.size() == 1) //表明有一對相同種類的
				res = max(res, res1 + 1);
			else
				res = max(res, res1);
		}
	}
	if (j) {
		int res1 = solve(i, j - 1, k, r);
		set<int> &ss1 = s[i][j - 1][k][r];
		if (ss1.size() < 5) {
			if (ss1.size() - ss.size() == 1) //表明有一對相同種類的
				res = max(res, res1 + 1);
			else
				res = max(res, res1);
		}
	}
	if (k) {
		int res1 = solve(i, j, k - 1, r);
		set<int> &ss1 = s[i][j][k - 1][r];
		if (ss1.size() < 5) {
			if (ss1.size() - ss.size() == 1) //表明有一對相同種類的
				res = max(res, res1 + 1);
			else
				res = max(res, res1);
		}
	}
	if (r) {
		int res1 = solve(i, j, k, r - 1);
		set<int> &ss1 = s[i][j][k][r - 1];
		if (ss1.size() < 5) {
			if (ss1.size() - ss.size() == 1) //表明有一對相同種類的
				res = max(res, res1 + 1);
			else
				res = max(res, res1);
		}
	}
	if (res == -1) res = -2;
	return res;
}

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	while (scanf("%d", &n) == 1 && n) {
		FOR1(i, 0, n - 1) {
			FOR1(j, 0, 3) {
				cin >> mat[j][i];
			}
		}
		int ans = 0;
		FOR1(i, 0, n) {
			FOR1(j, 0, n) {
				FOR1(k, 0, n) {
					FOR1(r, 0, n) {
						dp[i][j][k][r] = -1; //表示未訪問過
						if (!i && !j && !k && !r) {
							dp[i][j][k][r] = 0;
							s[i][j][k][r].clear(); //注意下一組數據要重新初始化
						}
						set<int> &ss = s[i][j][k][r];
						if (i) {
							ss = s[i - 1][j][k][r];
							int &mm = mat[0][i - 1];
							if (s[i][j][k][r].count(mm)) s[i][j][k][r].erase(mm);
							else s[i][j][k][r].insert(mm);
						}
						else if (j) {
							ss = s[i][j - 1][k][r];
							int &mm = mat[1][j - 1];
							if (s[i][j][k][r].count(mm)) s[i][j][k][r].erase(mm);
							else s[i][j][k][r].insert(mm);
						}
						else if (k) {
							ss = s[i][j][k - 1][r];
							int &mm = mat[2][k - 1];
							if (s[i][j][k][r].count(mm)) s[i][j][k][r].erase(mm);
							else s[i][j][k][r].insert(mm);
						}
						else if (r) {
							ss = s[i][j][k][r - 1];
							int &mm = mat[3][r - 1];
							if (s[i][j][k][r].count(mm)) s[i][j][k][r].erase(mm);
							else s[i][j][k][r].insert(mm);
						}
						solve(i, j, k, r);
						ans = max(ans, dp[i][j][k][r]);
					}
				}
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}

習9-3 UVA 1629 切蛋糕

題意
有一個n行m列(1≤n,m≤20)的網格蛋糕上有一些櫻桃。每次可以用一刀沿着網格線把蛋糕切成兩塊,並且只能夠直切不能拐彎。要求最後每一塊蛋糕上恰好有一個櫻桃,且切割線總長度最小。如圖9-30所示是一種切割方法。

思路
一開始理解成了切割的次數最少,後來才發現是切割線總長度最小。
就是一個比較直接的DP。

代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
#include <set>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int MAXN = 21;

int n, m, k;
int dp[MAXN][MAXN][MAXN][MAXN]; //dp[i][j][p][q]表示已經邊界爲i和j、長寬p和q的矩形區域需要切的最少刀數
int C[MAXN][MAXN][MAXN][MAXN]; //表示相應區域內的櫻桃數量
int A[MAXN][MAXN]; //表示矩形地圖

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	int kase = 0;
	while (scanf("%d %d %d", &n, &m, &k) == 3) {
		memset(A, 0, sizeof(A));
		int x, y;
		FOR1(i, 0, k - 1) {
			scanf("%d %d", &x, &y);
			A[x-1][y-1] = 1;
		}
		FOR1(j, 1, n) {
			FOR1(i, 0, n - j) {
				FOR1(q, 1, m) {
					FOR1(p, 0, m - q) {
						if (j == 1 && q == 1) {
							dp[i][j][p][q] = 0;
							C[i][j][p][q] = A[i][p];
							continue;
						}
						dp[i][j][p][q] = 0x3f3f3f3f; //相當於無窮大
						if (j > 1) {
							C[i][j][p][q] = C[i][j - 1][p][q] + C[i + j - 1][1][p][q];
							if (C[i][j][p][q] <= 1) {
								dp[i][j][p][q] = 0;
								continue;
							}
							FOR1(jj, 1, j - 1) {
								if (C[i][jj][p][q] && C[i + jj][j - jj][p][q])
									dp[i][j][p][q] = min(dp[i][j][p][q], dp[i][jj][p][q] + dp[i + jj][j - jj][p][q] + q);
							}
						}
						if (q > 1) {
							C[i][j][p][q] = C[i][j][p][q - 1] + C[i][j][p + q - 1][1];
							if (C[i][j][p][q] <= 1) {
								dp[i][j][p][q] = 0;
								continue;
							}
							FOR1(qq, 1, q - 1) {
								if (C[i][j][p][qq] && C[i][j][p + qq][q - qq])
									dp[i][j][p][q] = min(dp[i][j][p][q], dp[i][j][p][qq] + dp[i][j][p + qq][q - qq] + j);
							}
						}
					}
				}
			}
		}
		printf("Case %d: %d\n", ++kase, dp[0][n][0][m]);
	}
	return 0;
}

習9-4 UVA 1630 串摺疊(未嘗試)

題意
給出一個由大寫字母組成的長度爲n(1≤n≤100)的串,“摺疊”成一個儘量短的串。例如,AAAAAAAAAABABABCCD摺疊成9(A)3(AB)CCD。摺疊是可以嵌套的,例如,NEERCYESYESYESNEERCYESYESYES可以摺疊成2(NEERC3(YES))。多解時可以輸出任意解。

思路

代碼



習9-5 UVA 242 郵票和信封

題意
假定一張信封最多貼5張郵票,如果只能貼1分和3分的郵票,可以組成面值1~13以及15,但不能組成面值14。我們說:對於郵票組合{1,3}以及數量上限S=5,最大連續郵資爲13。1~13和15的組成方法如表9-3所示。
輸入S(S≤10)和若干郵票組合(郵票面值不超過100),選出最大連續郵資最大的一個組合。如果有多個並列,郵票組合中郵票的張數應最多。如果還有並列,郵票從大到小排序後字典序應最大。

思路
題目不難,就是一個完全揹包的滾動DP。但是有很多細節需要注意,我WA了多次:
1、連續郵資需要從1開始計數。
2、所給的郵票序列可能是亂序的,輸出要按照最開始的順序,而不是排過序的順序。
3、先比較總值,再比較張數,最後比較字典序,這個順序審題要清楚
4、注意輸出格式跟一般的題目不太一樣

代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
#include <set>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int MAXN = 11;
const int MAXD = 101;

int S, n;
int A[MAXN][MAXN], B[MAXN][MAXN];
int c[MAXN];
int dp[MAXN*MAXD]; //dp[i]表示得到值爲i的郵資至少需要dp[i]張郵票

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	while (scanf("%d", &S) == 1 && S) {
		scanf("%d", &n);
		FOR1(i, 0, n - 1) {
			scanf("%d", &c[i]);
			FOR1(j, 0, c[i] - 1) {
				scanf("%d", &A[i][j]);
				B[i][j] = A[i][j];
			}
			sort(A[i], A[i] + c[i]); //排序爲從小到大
		}

		int maxi = -1, maxcnt = -1;
		FOR1(i, 0, n - 1) {
			memset(dp, 0x3f, sizeof(dp));
			dp[0] = 0;
			int big = A[i][c[i] - 1] * S; //由於排序爲從小到大
			FOR1(j, 0, c[i] - 1) {
				int &a = A[i][j];
				FOR1(k, 0, S - 1) {
					FOR2(r, big, a) {
						if (dp[r - a] < S) dp[r] = min(dp[r], dp[r - a] + 1);

					}
				}
			}
			int cnt = 0;
			FOR1(r, 1, big) {
				if (dp[r] == 0x3f3f3f3f) break;
				else cnt++;
			}
			bool change = false;
			if (cnt > maxcnt) change = true; //連續數量更多
			if (cnt == maxcnt) {
				if (c[maxi] != c[i]) { //張數不一樣
					if (c[maxi] > c[i]) change = true;
				}
				else {
					FOR1(j, 1, c[i]) { //張數一樣,則從大到小按照字典序比較(注意小的話在前)
						if (A[i][c[i] - j] < A[maxi][c[maxi] - j]) {
							change = true;
							break;
						}
						else if (A[i][c[i] - j] > A[maxi][c[maxi] - j])
							break;
					}
				}
			}
			if (change) {
				maxi = i;
				maxcnt = cnt;
			}
		}

		printf("max coverage = %3d :", maxcnt); //注意輸出格式
		FOR1(j, 0, c[maxi] - 1)
			printf("%3d", B[maxi][j]);
		printf("\n");


	}
	return 0;
}

習9-6 UVA 10723 電子人的基因

題意
輸入兩個A~Z組成的字符串(長度均不超過30),找一個最短的串,使得輸入的兩個串均是它的子序列(不一定連續出現)。你的程序還應統計長度最短的串的個數。例如,ABAAXGF和AABXFGA的最優解之一爲AABAAXGFGA,一共有9個解。

思路
dp[i][j]表示兩個序列分別前i個和前j個的最短公共序列,DP即可。
注意要用long long計數。

代碼

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

#define LOOP1(i, a, b) for (int i = (a); i <= (b); i++)

const int N = 31;
const int INF = 0x3f3f3f3f;

char s[2][N];
int len[2];
long long dp[N][N][2];

int main(void)
{
#ifdef DEBUG
    freopen("datain", "r", stdin);
#endif
    int kase = 0;
    scanf("%d", &kase);
    getchar();
    LOOP1(k, 0, kase-1) {
        LOOP1(i, 0, 1) {
            cin.getline(s[i], N);
            len[i] = strlen(s[i]);
        }
        LOOP1(i, 0, len[0]) {
            LOOP1(j, 0, len[1]) {
                dp[i][j][0] = 0;
                if (i == 0 || j == 0) dp[i][j][1] = 1;
                else dp[i][j][1] = 0;
            }
        }
        LOOP1(i, 1, len[0]) {
            LOOP1(j, 1, len[1]) {
                if (s[0][i-1] == s[1][j-1]) { 
                    dp[i][j][0] = dp[i-1][j-1][0] + 1;
                    dp[i][j][1] = dp[i-1][j-1][1];
                } else {
                    dp[i][j][0] = max(dp[i-1][j][0], dp[i][j-1][0]);
                    if (dp[i][j][0] == dp[i-1][j][0])
                        dp[i][j][1] += dp[i-1][j][1];
                    if (dp[i][j][0] == dp[i][j-1][0])
                        dp[i][j][1] += dp[i][j-1][1];
                }
            }
        }

        printf("Case #%d: %lld %lld\n", k+1, len[0] + len[1] - dp[len[0]][len[1]][0], dp[len[0]][len[1]][1]);
    }

    return 0;
}

習9-7 UVA 1631 密碼鎖

題意
有一個n(n≤1000)位密碼鎖,每位都是0~9,可以循環旋轉。每次可以讓1~3個相鄰數字同時往上或者往下轉一格。例如,567890->567901(最後3位向上轉)。輸入初始狀態和終止狀態(長度不超過1000),問最少要轉幾次。例如,111111到222222至少轉2次,由896521到183995則要轉12次。

思路
記錄:
1、函數名next()要慎用,可能會跟命名空間中的函數重複(與本題無關)。
2、這個題是標準的BFS記憶搜索,但我在寫程序的時候用了遞歸,反而不是BFS了。
3、思路上的錯誤,想當然認爲應該根據當前位置距離目標位置的遠近來決定搜索方向,卻忽略了反方向反而可能更快的情況。例如:
896521 183995
我的輸出是13,而答案是12。
4、DP的時候後三位不需要合成一個數,那樣計算更復雜,程序寫起來也麻煩,不如用dp[k][a][b][c]好。
5、開始串和目標串後面分別補3個0,這樣程序就不需要特殊處理了,最後輸出dp[n][0]即可。

代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
#include <string>
#include <queue>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

typedef pair<int, int> P;

const int MAXN = 1000 + 5;

int n;
int st[MAXN], se[MAXN]; //初始串和目標串
int dp[MAXN][1001]; //dp[i][j]表示到達前面k個已經調整好且後面3個數字組合爲s(反向)的狀態時,需要的最少步數

int nextx(int a, int d) {
	if (a == 0 && d == -1) return 9;
	if (a == 9 && d == 1) return 0;
	return a + d;
}

void solve() { //標準BFS
	memset(dp, 0x3f, sizeof(dp)); //相當於無限步
	queue<P> que;
	int k, x = st[0] + 10 * st[1] + 100 * st[2];
	dp[0][x] = 0;
	que.push(P(0, x));
	while (!que.empty()) {
		int qn = que.size();
		FOR1(j, 0, qn - 1) {
			P p = que.front(); que.pop();
			k = p.first;
			x = p.second;
			if (k == n) continue;
			int a = x % 10, b = se[k];

			int nx;
			if (a == b) {
				nx = x / 10 + 100 * st[k + 3]; 
				if (dp[k + 1][nx] == 0x3f3f3f3f) {
					dp[k + 1][nx] = dp[k][x];
					que.push(P(k + 1, nx));
					//printf("%d %d %d\n", k + 1, nx, dp[k + 1][nx]);
				}
				continue;
			}

			for (int d = -1; d <= 1; d += 2) {
				int x1 = x % 10, x2 = x / 10 % 10, x3 = x / 100;
				int nx1, nx2, nx3;
				nx1 = nextx(x1 % 10, d);
				nx2 = nextx(x2 % 10, d);
				nx3 = nextx(x3 % 10, d);
				nx = nx1 + x / 10 * 10;
				if (dp[k][nx] == 0x3f3f3f3f) {
					que.push(P(k, nx));
					dp[k][nx] = dp[k][x] + 1;
					//printf("%d %d %d\n", k, nx, dp[k][nx]);
				}
				nx = nx1 + nx2 * 10 + x3 * 100;
				if (k < n - 1 && dp[k][nx] == 0x3f3f3f3f) {
					que.push(P(k, nx));
					dp[k][nx] = dp[k][x] + 1;
					//printf("%d %d %d\n", k, nx, dp[k][nx]);
				}
				nx = nx1 + nx2 * 10 + nx3 * 100;
				if (k < n - 2 && dp[k][nx] == 0x3f3f3f3f) {
					que.push(P(k, nx));
					dp[k][nx] = dp[k][x] + 1;
					//printf("%d %d %d\n", k, nx, dp[k][nx]);
				}
			}
		}
	}
}

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	char s0[MAXN];
	while (scanf("%s", s0) != EOF) {
		n = strlen(s0);
		FOR1(i, 0, strlen(s0) - 1) st[i] = s0[i] - 48;
		scanf("%s", s0);
		FOR1(i, 0, strlen(s0) - 1) se[i] = s0[i] - 48;
		st[n] = st[n + 1] = st[n + 2] = 0;
		se[n] = se[n + 1] = se[n + 2] = 0;
		
		solve();
		printf("%d\n", dp[n][0]);
	}
	return 0;
}

習9-8 UVA 1632 阿里巴巴

題意
直線上有n(n≤10000)個點,其中第i個點的座標是xi,且它會在di秒之後消失。Alibaba可以從任意位置出發,求訪問完所有點的最短時間。無解輸出No solution。

思路
dp[i][j][k]表示已訪問的區間長度爲i,起始端點爲j,人的狀態爲k(0表示在左端點,1表示右)情況下最少用時爲d,然後DP即可。
注意細節問題:
1、如果到達某點時該點正好消失,則失敗。我原來的判斷是成功,所以錯了。
2、時間是從0開始的。

代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int MAXN = 10001;
const int INF = 0x3f3f3f3f;

struct Point {
	int x, d;
	bool operator < (const Point &a) const {
		return x != a.x ? (x < a.x) : (d < a.d);
	}
};

int n;
Point A[MAXN];
int dp[2][MAXN][2]; //dp[i][j][k]表示已訪問的區間長度爲i,起始端點爲j,人的狀態爲k(0表示在左端點,1表示右)情況下最少用時爲d

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	while (scanf("%d", &n) == 1) {
		FOR1(i, 0, n - 1) scanf("%d %d", &A[i].x, &A[i].d);
		sort(A, A + n); //按照x軸順序排序

		memset(dp, 0x3f, sizeof(dp));
		FOR1(i, 0, n - 1) {
			FOR1(j, 0, n - 1 - i) {
				if (i == 0) {
					dp[i & 1][j][0] = dp[i & 1][j][1] = 0;
					continue;
				}
				dp[i & 1][j][0] = dp[i & 1][j][1] = INF;
				int t;
				if (j + i > 0) { //上一步是走到下個區間右端點,需要右端點大於0
					if ((t = dp[(i - 1) & 1][j][0] + A[j + i].x - A[j].x) < A[j + i].d)
						dp[i & 1][j][1] = min(dp[i & 1][j][1], t); //從上個區間左端點走
					if ((t = dp[(i - 1) & 1][j][1] + A[j + i].x - A[j + i - 1].x) < A[j + i].d)
						dp[i & 1][j][1] = min(dp[i & 1][j][1], t); //從上個區間右端點走
				}
				if (j < n - 1) { //上一步是走到下個區間左端點,需要左端點小於n-1
					if ((t = dp[(i - 1) & 1][j + 1][0] + A[j + 1].x - A[j].x) < A[j].d)
						dp[i & 1][j][0] = min(dp[i & 1][j][0], t); //從上個區間左端點走
					if ((t = dp[(i - 1) & 1][j + 1][1] + A[j + i].x - A[j].x) < A[j].d)
						dp[i & 1][j][0] = min(dp[i & 1][j][0], t); //從上個區間右端點走
				}
			}
		}

		int ans = min(dp[(n - 1) & 1][0][0], dp[(n - 1) & 1][0][1]);
		if (ans < INF) printf("%d\n", ans);
		else printf("No solution\n");
	}
	return 0;
}

習9-9 UVA 10163 倉庫守衛

題意
你有n(n≤100)個相同的倉庫。有m(m≤30)個人應聘守衛,第i個應聘者的能力值爲Pi(1≤Pi≤1000)。每個倉庫只能有一個守衛,但一個守衛可以看守多個倉庫。如果應聘者i看守k個倉庫,則每個倉庫的安全係數爲Pi/K的整數部分。沒人看守的倉庫安全係數爲0。
你的任務是招聘一些守衛,使得所有倉庫的最小安全係數最大,在此前提下守衛的能力值總和(這個值等於你所需支付的工資總和)應最小。

思路
這個題目的關鍵在於需要做兩次DP,第一次是求最小安全係數,第二次是求工資總和最小方案。
第一次的DP可以用二分法,效率更高。

代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
#include <functional>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int MAXN = 101;
const int MAXM = 31;
const int MAXP = 1001;
const int INF = 0x3f3f3f3f;

int n, m;
int P[MAXM];
int dp[MAXM][MAXP]; //dp[i][j]表示前i個守衛在倉庫最小安全係數爲j情況下能看的最多倉庫數量(還可以考慮用一維數組或滾動數組)
int cost[MAXN][MAXM]; //第二次DP,求最少花費

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	while (scanf("%d %d", &n, &m) == 2 && n) {
		FOR1(i, 0, m - 1) scanf("%d", &P[i]);
		sort(P, P + m, greater<int>()); //按照能力值大小排序

		int res[MAXP]; //表示最小安全係數爲j且能看倉庫數量不小於n時最少守衛數
		int L = 0, K = 0;
		memset(res, 0x3f, sizeof(res));
		memset(dp, 0x3f, sizeof(dp));
		FOR2(j, P[0], 1) { //最小安全係數爲j
			FOR1(i, 0, m) { //用了i個守衛
				if (i == 0) {
					dp[i][j] = 0;
					continue;
				}
				dp[i][j] = dp[i - 1][j] + P[i - 1] / j;
				if (dp[i][j] >= n) res[j] = min(res[j], i);
			}
			if (res[j] <= m) {
				L = j; //最小安全係數的最大值
				K = res[j]; //相應需要的守衛數量
				break;
			}
		}

		if (L == 0) {
			printf("0 0\n");
			continue;
		}
		memset(cost, 0x3f, sizeof(cost));
		memset(cost[0], 0, sizeof(cost[0]));
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				cost[i][j] = cost[i][j - 1];
				for (int k = 1; k <= i; k++) {
					int safe = P[j-1] / k;
					if (safe >= L)
						cost[i][j] = min(cost[i][j], cost[i - k][j - 1] + P[j-1]);
				}
			}
		}
		printf("%d %d\n", L, cost[n][m]);
	}
	return 0;
}

習9-10 UVA 10641 照亮體育館(未嘗試)

題意
輸入一個凸n(3≤n≤30)邊形體育館和多邊形外的m(1≤m≤1000)個點光源,每個點光源都有一個費用值。選擇一組點光源,照亮整個多邊形,使得費用值總和儘量小。如圖9-31所示,多邊形ABCDEF可以被兩組光源{1,2,3}和{4,5,6}照亮。光源的費用決定了哪組解更優。

思路

代碼



習9-11 UVA 1633 禁止的迴文字串

題意
輸入正整數n和k(1≤n≤400,1≤k≤10),求長度爲n的01串中有多少個不含長度至少爲k的迴文連續子串。例如,n=k=3時只有4個串滿足條件:001, 011, 100, 110。

思路
DP的關鍵是新增一個字符時,最後k個和k+1個字符不構成迴文串。因此要記錄最後k個字符。
dp[i][j]表示i位數在後面k位爲j的情況下的可能數。
需要先對長度爲m的迴文串進行統計,不然容易超時。

代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
#include <set>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int MAXN = 400;
const int MAXK = 10;
const int MOD = 1000000007;

int n, k;
int dp[MAXN + 1][1 << MAXK]; //dp[i][j]表示i位數在後面k位爲j的情況下的可能數
int huiwen[MAXK + 1][1 << (MAXK + 1)]; //如果是迴文串,則值爲0,否則爲1,後面方便乘

void pre_process() { //事先對長度爲m的迴文串進行統計
	FOR1(m, 0, MAXK + 1) {
		int L = 0;
		FOR1(i, 0, (1 << m) - 1) {
			if (m <= 1) {
				huiwen[m][i] = 0;
				break;
			}
			huiwen[m][i] = 1;
			if ((i >> (m - 1)) == (i & 1)) {
				int x = (i - (i & (1 << (m - 1)))) >> 1;
				huiwen[m][i] = huiwen[m - 2][x];
			}
		}
	}
}

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	pre_process();
	int T;
	cin >> T;
	FOR1(t, 1, T) {
		cin >> n >> k;
		int res;
		FOR1(m, 0, n) {
			if (m < k) { //如果總長度低於k則肯定不會出現不低於k長度的迴文串
				res = (1 << m);
				continue;
			}
			res = 0;
			FOR1(i, 0, (1 << k) - 1) {
				dp[m][i] = 0;
				if (huiwen[k][i] == 0) //末尾k個數爲迴文數
					dp[m][i] = 0;
				else if (m == k)
					dp[m][i] = 1;
				else {
					dp[m][i] = (dp[m][i] + huiwen[k + 1][i] * dp[m - 1][i >> 1]) % MOD; //末尾第k+1位爲0
					dp[m][i] = (dp[m][i] + huiwen[k + 1][(1 << k) + i] * dp[m - 1][(1 << (k - 1)) + (i >> 1)]) % MOD; //末尾第k+1位爲1
				}
				res = (res + dp[m][i]) % MOD;
			}
		}
		printf("%d\n", res);
	}
	return 0;
}

習9-12 UVA 12093 保衛Zonk(未嘗試)

題意
給定一個有n(n≤10000)個結點的無根樹。有兩種裝置A和B,每種都有無限多個。

  • 在某個結點X使用A裝置需要C1(C1≤1000)的花費,並且此時與結點X相連的邊都被覆蓋。
  • 在某個結點X使用B裝置需要C2(C2≤1000)的花費,並且此時與結點X相連的邊以及與結點X相連的點相連的邊都被覆蓋。

求覆蓋所有邊的最小花費。

思路

代碼



習9-13 UVA 1289 疊盤子

題意
有n(1≤n≤50)堆盤子,第i堆盤子有hi個盤子(1≤hi≤50),從上到下直徑不減。所有盤子的直徑均不超過10000。有如下兩種操作。

  • split:把一堆盤子從某個位置處分成上下兩堆。
  • join:把一堆盤子a放到另一堆盤子b的頂端,要求是a底部盤子的直徑不超過b頂端盤子的直徑。

你的任務是用最少的操作把所有盤子疊成一堆。

思路
顯然盤子是由小到大放置好的,所以要先進行排序,然後進行離散化。
這個題目的難點主要在於盤子直徑有可能有重複值。
每次處理離散化後直徑爲k的盤子時,一定會都移動在某一個直徑爲k的盤子上,因而需要記錄處理好至多爲k的盤子時這些盤子在那一堆上(肯定是直徑爲k的盤子上)。
所以dp[i][j]表示所有不大於i的數(已處理過)都放在堆j上,需要的最少步數。

代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <set>
#include <map>
#include <vector>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int MAXN = 51;
const int MAXH = 51;
const int INF = 0x3f3f3f3f;

int n, k;
int m[MAXN], L[MAXN][MAXH];
set<int> st[MAXN]; //記錄堆中包含的數
vector<int> vt[MAXN * MAXH]; //記錄數字所在的堆
int dp[MAXN * MAXH][MAXN]; //dp[i][j]表示所有不大於i的數(已處理過)都放在堆j上,需要的最少步數

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	int kase = 0;
	while (scanf("%d", &n) == 1) {
		set<int> s;
		FOR1(i, 0, n - 1) {
			scanf("%d", &m[i]);
			FOR1(j, 0, m[i] - 1) {
				scanf("%d", &L[i][j]);
				s.insert(L[i][j]);
			}
		}
		map<int, int> mp;
		k = 0;
		for (set<int>::iterator it = s.begin(); it != s.end(); it++)
			mp[*it] = ++k;
		FOR1(i, 0, n - 1) st[i].clear();
		FOR1(i, 0, k) vt[i].clear();
		FOR1(i, 0, n - 1) {
			FOR1(j, 0, m[i] - 1) {
				int &l = L[i][j];
				l = mp[l]; //轉換爲數字1-n
				st[i].insert(l);
				if (vt[l].empty() || vt[l][vt[l].size() - 1] != i)
					vt[l].push_back(i);
			}
		}

		int res = INF;
		memset(dp, 0x3f, sizeof(dp));
		FOR1(i, 1, k) {
			int c = vt[i].size();
			FOR1(r, 0, c - 1) { //數字i只可能最後移動到有i的堆上
				int j = vt[i][r];
				if (i == 1) {
					dp[i][j] = c - 1;
				}
				else {
					int p = vt[i - 1].size();
					FOR1(q, 0, p - 1) { //遍歷i-1所在位置
						int x = vt[i - 1][q];
						if (x == j) { //i所在位置與i-1所在位置爲同一堆
							if (c == 1) dp[i][j] = min(dp[i][j], dp[i - 1][x]); //只有一個i,無需移動
							else dp[i][j] = min(dp[i][j], dp[i - 1][x] + c); //有多個i,則先要把這個i-1移下去後面再移上來
						}
						else {
							if (st[x].count(i)) //說明i-1所在的堆中有i,則i-1無需額外移動
								dp[i][j] = min(dp[i][j], dp[i - 1][x] + c - 1);
							else dp[i][j] = min(dp[i][j], dp[i - 1][x] + c); //說明i-1所在的堆中沒有i,則i-1需要額外移動
						}
					}
				}
				if (i == k) res = min(res, dp[i][j]);
			}
		}
		printf("Case %d: %d\n", ++kase, 2 * res - n + 1);
	}
	return 0;
}

習9-14 UVA 1543 圓和多邊形

題意
給你一個圓和圓周上的n(3≤n≤40)個不同點。請選擇其中的m(3≤m≤n)個,按照在圓周上的順序連成一個m邊形,使得它的面積最大。例如,在圖9-32中,右上方的多邊形最大。

思路
這個題思路還是比較清晰的,完全類似於一個數組的DP來做即可。
dp[i][j][k]表示從標號爲j的點開始的i個點中一共選了k個點(首尾點一定選中),所組成的多邊形的最大面積。
每次DP加一個三角形。

代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <set>
#include <map>
#include <vector>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int MAXN = 41;
const double PI = 3.14159265358979323846;
const int INF = 0x3f3f3f3f;

int n, m;
double p[MAXN];
double dp[MAXN][MAXN][MAXN]; //dp[i][j][k]表示從標號爲j的點開始的i個點中一共選了k個點(首尾點一定選中),所組成的多邊形的最大面積 

double squ(double a, double b) { //給定兩點座標求對應扇形中小三角形面積
	double theta = (b - a) * PI;
	if (theta < 0) theta += PI;
	return sin(theta) * cos(theta);
}

double tri(double a, double b, double c) { //求三角形面積
	return squ(a, b) + squ(b, c) + squ(c, a);
}

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	while (scanf("%d %d", &n, &m) == 2 && n) {
		FOR1(i, 1, n)
			scanf("%lf", &p[i]);

		double ans = 0;
		memset(dp, 0, sizeof(dp));
		FOR1(i, 2, n) {
			FOR1(j, 1, n - i + 1) {
				FOR1(k, 2, min(i, m)) {
					if (k == 2) {
						dp[i][j][k] = 0;
						continue;
					}
					FOR1(r, j + 1, j + i - 2) { //中間分隔點
						dp[i][j][k] = max(dp[i][j][k], dp[r - j + 1][j][k - 1] + tri(p[j], p[r], p[j + i - 1]));
					}
					if (k == m) 
						ans = max(ans, dp[i][j][k]);
				}
			}
		}
		printf("%.6lf\n", ans);
	}
	return 0;
}

習9-15 UVA 12589 學習向量

題意
輸入n個向量(x,y)(0≤x,y≤50),要求選出k個,從(0,0)開始畫,使得畫出來的折線與x軸圍成的圖形面積最大。例如,4個向量是(3,5), (0,2), (2,2), (3,0),可以依次畫(2,2), (3,0),(3,5),圍成的面積是21.5,如圖9-33所示。輸出最大面積的兩倍。1≤k≤n≤50。

思路
基本思路是先按照y/x也就是斜率進行排序,斜率高的在前面。
然後DP從其中選擇k個即可。

代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
#include <set>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int MAXN = 50;

struct Vec {
	int x, y;
	bool operator < (const Vec& a) const {
		if (x * a.y != y * a.x) return x * a.y < y * a.x;
		if (y != a.y) return y < a.y;
		return x < a.x;
	}
};

int n, k;
Vec V[MAXN];

int dp[MAXN + 1][MAXN + 1][MAXN*MAXN + 1]; //dp[i][j][m]表示前面i個向量中選擇了j個且最終高度爲m的情況下,能達到的最大面積*2

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	int T;
	cin >> T;
	FOR1(t, 1, T) {
		cin >> n >> k;
		FOR1(i, 0, n - 1)
			cin >> V[i].x >> V[i].y;
		sort(V, V + n); //斜率高的排在前面
		memset(dp, 0xff, sizeof(dp)); //每個數爲-1
		dp[0][0][0] = 0;
		if (t == 310)
			t = t;
		int big = 0;
		FOR1(i, 1, n) {
			int &x = V[i - 1].x;
			int &y = V[i - 1].y;
			big += y;
			FOR1(j, 0, min(i, k)) {
				FOR1(m, 0, big) {
					dp[i][j][m] = dp[i - 1][j][m]; //沒有選擇當前向量
					if (j && m >= y && dp[i - 1][j - 1][m - y] >= 0) //選擇了當前向量
						dp[i][j][m] = max(dp[i][j][m], dp[i - 1][j - 1][m - y] + x * (2 * m - y));
				}
			}
		}
		int res = 0;
		FOR1(m, 0, big) res = max(res, dp[n][k][m]);
		printf("Case %d: %d\n", t, res);
		//printf("%d %d\n", n, k);
		//FOR1(i, 0, n - 1)
		//	printf("%d %d\n", V[i].x, V[i].y);
	}
	return 0;
}

習9-16 UVA 1634 野餐(未嘗試)

題意
輸入m(m≤100)個點,選出其中若干個點,以這些點爲頂點組成一個面積最大的凸多邊形,使得內部沒有輸入點(邊界上可以有)。輸入點的座標各不相同,且至少有3個點不共線,如圖9-34所示。

思路

代碼



習9-17 UVA 10271 佳佳的筷子

題意
中國人吃飯喜歡用筷子。佳佳與常人不同,他的一套筷子有3只,兩根短筷子和一隻比較長的(一般用來穿香腸之類的食物)。兩隻較短的筷子的長度應該儘可能接近,但是最長那隻的長度無須考慮。如果一套筷子的長度分別是A,B,C(A≤B≤C),則用(A-B)2的值表示這套筷子的質量,這個值越小,這套筷子的質量越高。
佳佳請朋友吃飯,並準備爲每人準備一套這種特殊的筷子。佳佳有N(N≤1000)只筷子,他希望找到一種辦法搭配好K+8套筷子,使得這些筷子的質量值和最小。保證筷子足夠,即3K+24≤N。
提示:需要證明一個猜想。

思路
將筷子從大到小排序。
dp[i][j]表示的是前i根筷子前j個人最少的badness是多少。
那麼就要考慮第i跟筷子選不選給第j個人,如果選的話就是dp[ i -2][j - 1] + (c[i] - c[i - 1])的平方(因爲選了第i根筷子的話就要選第i - 1根筷子),如果不選的話,那麼就是dp[i -1][j]。
參考鏈接:https://blog.csdn.net/vv494049661/article/details/51419395

代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <functional>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int MAXN = 5001; 
const int MAXK = 1001;
const int INF = 1001;

int K, N;
int A[MAXN];
int dp[MAXN][MAXK]; //dp[i][j]表示前i個裏面(從大到小排序)選了j組時最小總bad數

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	int T;
	cin >> T;
	FOR1(t, 1, T) {
		cin >> K >> N;
		K += 8;
		FOR1(i, 1, N)
			scanf("%d", &A[i]);
		sort(A + 1, A + 1 + N, greater<int>());

		memset(dp, 0, sizeof(dp));
		FOR1(i, 3, N) {
			FOR1(j, 1, min(K, i / 3)) {
				dp[i][j] = dp[i - 2][j - 1] + (A[i] - A[i - 1]) * (A[i] - A[i - 1]);
				if (i > 3 * j)
					dp[i][j] = min(dp[i][j], dp[i - 1][j]);
			}
		}
		printf("%d\n", dp[N][K]);
	}
	return 0;
}

習9-18 UVA 137 棒球投手

題意
你經營着一支棒球隊。在接下來的g+10天中會有g(3≤g≤200)場比賽,其中每天最多一場比賽。你已經分析出你的n(5≤n≤100)個投手中每個人對陣所有m(3≤m≤30)個對手的勝率(一個n*m矩陣),要求給出作戰計劃(即每天使用哪個投手),使得總獲勝場數的期望值最大。注意,一個投手在上場一次後至少要休息4天。
提示:如果直接記錄前4天中每天上場的投手編號1~n,時間和空間都無法承受。

思路
DP的關鍵在於每次只有當天前五勝率得選手可能上場,否則不會是最優。這樣時間和空間就大大降低了。
問題:
1、這個題的測試數據有坑,m的範圍最大可以達到100,因爲我把範圍設到99都會報RE錯誤。
2、寫代碼犯了低級錯誤,把sort(p[i] + 1, p[i] + 1 + n)寫成了sort(p[i] + 1, p[i + 1 + n])。
3、要想將d[k]=0以及非0的情況合併到同一個循環中,p[0][0].id = 0, p[0][0].p = 0這些初始化是不可缺少的。而且這樣寫代碼會很巧妙。

代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <functional>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int MAXN = 101;
const int MAXM = 101; //題目中給的範圍是30,實際上測試數據能到100
const int MAXG = 211;

struct P {
	int id; //表示id
	int p; //表示概率
	bool operator < (const P& x) const {
		return p > x.p;
	}
};

int n, m, g;
P p[MAXM][MAXN];
int d[MAXG];
int dp[2][6][6][6][6]; //滾動數組,後四個數表示最後四場上的隊員

bool same(int i1, int j1, int i2, int j2) {
	return p[d[i1]][j1].id && p[d[i2]][j2].id && p[d[i1]][j1].id == p[d[i2]][j2].id;
}

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	int T;
	cin >> T;
	FOR1(t, 1, T) {
		cin >> n >> m >> g;
		FOR1(i, 1, m) {
			FOR1(j, 1, n) {
				cin >> p[i][j].p;
				p[i][j].id = j;
			}
			sort(p[i] + 1, p[i] + 1 + n);
			p[i][0].id = 0, p[i][0].p = 0;
		}
		p[0][0].id = 0, p[0][0].p = 0;
		g += 10;
		FOR1(i, 1, g) cin >> d[i];

		int ans = 0;
		memset(dp, 0, sizeof(dp));
		FOR1(k, 1, g) {
			int k1 = k & 1, k0 = (k - 1) & 1;
			memset(dp[k1], 0, sizeof(dp[k1]));
			FOR1(x, 0, 5) {
				if (d[k] == 0 && x) break;
				if (d[k] && !x) continue;
				FOR1(i, 0, 5) {
					if (k > 1 && same(k, x, k - 1, i)) continue;
					FOR1(j, 0, 5) {
						if (k > 2 && same(k, x, k - 2, j)) continue;
						FOR1(r, 0, 5) {
							if (k > 3 && same(k, x, k - 3, r)) continue;
							FOR1(s, 0, 5) {
								if (k > 4 && same(k, x, k - 4, s)) continue;
								ans = max(ans, dp[k1][x][i][j][r] = max(dp[k1][x][i][j][r], dp[k0][i][j][r][s] + p[d[k]][x].p));
							}
						}
					}
				}
			}
		}
		printf("%.2lf\n", ans * 0.01);
	}
	return 0;
}

習9-19 UVA 1443 花環(未嘗試)

題意
你的任務是用n(n≤40000)條等長細繩組成一個花環。每條細繩上都有一顆珍珠,重量爲wi(1≤wi≤10000)。花環應由m(2≤m≤10000)個片段組成,每個片段必須包含連續的偶數條細繩。每個片段的一半稱爲“半段”(兩個半段包含相同數量的細繩),每個“半段”最多能有d(1≤d≤10000)條細繩。你的任務是讓最重的半段儘量輕。如圖9-35所示,12條細繩的最優解是如下的3個片段,最重的半段的重量爲6(左數第1, 4, 6個半段)。

思路

代碼



習9-20 UVA 12222 山路(未嘗試)

題意
有一條狹窄的山路只有一個車道,因此不能有兩輛相反方向的車同時駛入。另外,爲了確保安全,對於山路上的任意一點,相鄰的兩輛同向行駛的車通過它的時間間隔不能少於10秒。給定n(1≤n≤200)輛車的行駛方向、到達時刻(對於往右開的車來說是到達山路左端點的時刻,而對於往左開的車來說是指到達右端點的時刻),以及行駛完山路的最短時間(爲了保證安全,實際行駛時間可以高於這個值),輸出最後一輛車離開山路的最早時刻。輸入保證任意兩輛車的到達時刻均不相同。
提示:本題的主算法並不難,但是實現細節需要仔細推敲。

思路

代碼



習9-21 UVA 1371 週期(未嘗試)

題意
兩個串的編輯距離爲進行的修改、刪除和插入操作次數的最小值(每次一個字符)。如圖9-36所示,A=abcdefg和B=ahcefig的編輯距離爲3。
如果x可以分成若干部分,使得每部分和y的編輯距離都不超過k,則y是x的k-近似週期。例如,x=abcdabcabb,y=abc,x可以分解爲abcd+abc+abb,3部分和y的編輯距離分別爲1, 0,1,因此y是x的1-近似週期。
輸入由小寫字母組成的x和y,求最小的k使得y是x的k-近似週期。|y|≤50,|x|≤5000。
提示:直接想出的動態規劃算法很可能太慢,要想辦法降低時間複雜度。
思路

代碼



習9-22 UVA 1579 俄羅斯套娃(未嘗試)

題意
桌上有n(n≤500)個套娃排成一行,你的任務是把它們套成若干個套娃組,使得每個套娃組內的套娃編號恰好是從1開始的連續編號。操作規則如下:

  • 只能把小的套在大的裏面,大小相等的套娃相互不能套。
  • 每次只能把兩個相鄰的套娃組合併成一個套娃組。
  • 一旦有兩個套娃屬於同一個組,它們永遠都屬於同一個組(只有與相鄰組合並的過程中
    會臨時拆散)。

執行合併操作的前後,所有套娃都是關閉的。爲了合併兩個套娃組,你需要交替地把一些套娃打開、重新套起來、關閉。例如,爲了合併[1, 2, 6]和[4],需要打開套娃6和4;爲了合併[1, 2, 5]和[3, 4],需要打開套娃5, 4, 3(只有先打開4才能打開3)。要求打開/關閉的總次數最少。無解輸出impossible。例如,“1 2 3 2 4 1 3”需要打開7次,如表9-4所示。

思路

代碼



習9-23 UVA 1322 優化最大值電路(未嘗試)

題意
所謂Maximizer,就是一個n輸入1輸出的硬件電路,它可以用若干個串行Sorter來實現,其中每個Sorter(i,j)表示把第i~j個輸入從小到大排序。最後一個Sorter的第n個輸出就是整個Maximizer的輸出。輸入一個由m個Sorter組成的Maximizer,保留儘量少的Sorter(順序不變),使得Maximizer仍能正常工作。n≤50000,m≤500000。

思路

代碼



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章