算法竞赛入门经典(第二版)-刘汝佳-第九章 动态规划初步 习题(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。

思路

代码



  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值