[区间DP] 例题

尚未写完, 过几天会加几道蓝题紫题


咕咕咕~



P1063 [NOIP2006 提高组] 能量项链

不难, 可以算模板题了.
d[l][r] 为区间 [l,r] 上能获取的最大值,
\(d[l][r] = max(d[l][i] + a[l] * a[i] * a[r] + d[i][r]), l<i<r\),
最终答案为 d[1][n].

#include <stdio.h>
#include <algorithm>
#define r l + len - 1

int n;
int a[203];
int d[203][203];

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]), a[n + i] = a[i];
	for (int len = 3; len <= n * 2; len++)
		for (int l = 1; r <= 2 * n; l++) 
			for (int i = l + 1; i < r; i++)
				d[l][r] = std:: max(d[l][r], d[l][i] + a[l] * a[i] * a[r] + d[i][r]);
	
	int ans = 0;
	for (int i = 1; i <= n; i++)
		ans = std:: max(ans, d[i][i + n]);
	printf("%d\n", ans);
	return 0;
}

P1040 [NOIP2003 提高组] 加分二叉树

不难, 个人认为注释很清楚, 注意 root[l][r] 是指 d[l][r] 最小时的根, 以供输出最终方案.

#include <stdio.h>
#define ci const int &

int n;
int a[53]; 
int d[53][53];
int root[53][53];

inline void print2 (ci l, ci r) {
	if (l > r || r < 1 || l > n)
		return;
	if (l == r) {
		printf("%d ", l);
		return;
	}
	printf("%d ", root[l][r]);		// 根 
	print2(l, root[l][r] - 1);		// 左
	print2(root[l][r] + 1, r);		// 右 
	return;
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]), d[i][i] = a[i], root[i][i] = i;
	for (int len = 2; len <= n; len++) {
		for (int l = 1; l + len - 1 <= n; l++) {
			int r = l + len - 1; 
			
			// 情况 1, l 为根, 左子树为空 
			d[l][r] = d[l + 1][r] + d[l][l];
			root[l][r] = l;
			
			// 情况 2, i 为根, i∈(l,r) 
			for (int i = l + 1; i < r; i++)
				if (d[l][i - 1] * d[i + 1][r] + d[i][i] > d[l][r]) { 
					d[l][r] = d[l][i - 1] * d[i + 1][r] + d[i][i];
					root[l][r] = i; 
				} 
		}
	}
	printf("%d\n", d[1][n]);
	print2(1, n);
	printf("\n");
	return 0;
}

P4302 [SCOI2003]字符串折叠 | UVA1630 串折叠 Folding

题目的区别在于, 洛谷上的这题只需输出长度, 而 UVA 上需要输出最终字符

对于每一个区间 [l,r], 我们判断能否由两个区间合并得到和能否向后复制当前区间. 同时用 h 表示当前区间, mod 表示当前区间是如何得到的(两个都是输出最短字符用)

这里只贴 UVA 这题的代码了, 洛谷上的这题最终输出 d[1][n] 就可以了

#include <stdio.h>
#include <string.h>
#define ci const int &

int n = 0;
char a[105];
int d[103][103];
int h[103][103], mod[103][103];
inline int numlen (ci x) {
	if (x < 10)
		return 1;
	if (x < 100)
		return 2;
	return 3;
}
inline bool check (ci l, ci r, ci sr) {	// [l,r] 能否转为 [l,sr] 
	int len = r - l + 1, slen = sr - l + 1; 
	if (slen % len != 0)
		return false;
	for (int i = l; i <= sr; i++)
		if (a[i] != a[l + (i - l) % len])
			return false;
	return true;
}
inline void print (ci l, ci r) {
	if (mod[l][r] == 0) 
		for (int i = l; i <= r; i++)
			printf("%c", a[i]);
	else if (mod[l][r] == 1) {
		print(l, h[l][r]);
		print(h[l][r] + 1, r);
	}
	else {
		printf("%d(", (r - l + 1) / h[l][r]);
		print(l, l + h[l][r] - 1);
		printf(")"); 
	}
}
inline void sovle () {
	n = strlen(a + 1);
	memset(d, 0x3F, sizeof(d));
	for (int len = 1; len <= n; len++) 
		for (int l = 1; l + len - 1 <= n; l++) {
			int r = l + len - 1;
			if (len == 1)
				d[l][r] = len;
			else
			for (int i = l; i < r; i++)
				if (d[l][i] + d[i + 1][r] < d[l][r]) {
					d[l][r] = d[l][i] + d[i + 1][r];
					h[l][r] = i;
					mod[l][r] = 1;
				}
			for (int sr = r + len, cnt = 2; sr <= n && check(l, r, sr); sr += len, cnt++) {
				if (d[l][r] + 2 + numlen(cnt) < d[l][sr]) {
					d[l][sr] = d[l][r] + 2 + numlen(cnt);
					h[l][sr] = len;
					mod[l][sr] = 2;
				}
			}
		}
	print(1, n);
	printf("\n");
	return;
}
int main() {
	while (~scanf("%s", a + 1)) 
		sovle();
	return 0;
}

P4342 [IOI1998]Polygon

个人认为比上面那题难(熟悉套路了就会了), 因为有个小点可能注意不到:
因为两个负数相乘是正数, 所以我们考虑同时保存每个区间可以得到的最大值和最小值, 同时需要增长数组至 2n 长, \(ans = max(d[i][i+n-1])\) 其中 \(1 <= i <= n\), 然后输出每一个满足 \(d[i][i+n-1]= ans\) 的 i 值即可.

#include <stdio.h>
#include <algorithm>
#define ci const int &

int n;
char ch[103];
int d[103][103];
int f[103][103];

inline int mathsovle (ci x, ci y, const char &op) {
	return (op == 't' ? (x + y) : (x * y));
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf(" %c %d", &ch[i], &d[i][i]), 
		ch[n + i] = ch[i],
		d[n + i][n + i] = d[i][i], f[i][i] = d[i][i], f[n + i][n + i] = d[i][i];
	for (int len = 2; len <= n; len++) 
		for (int l = 1; l + len - 1 <= 2 * n; l++) {
			int r = l + len - 1;
			d[l][r] = -0x3F3F3F3F, f[l][r] = 0x3F3F3F3F;
			for (int i = l; i < r; i++)
				d[l][r] = std:: max(d[l][r], mathsovle(d[l][i], d[i + 1][r], ch[i + 1])),
				d[l][r] = std:: max(d[l][r], mathsovle(f[l][i], f[i + 1][r], ch[i + 1])),
				d[l][r] = std:: max(d[l][r], mathsovle(d[l][i], f[i + 1][r], ch[i + 1])),
				d[l][r] = std:: max(d[l][r], mathsovle(f[l][i], d[i + 1][r], ch[i + 1])),
				f[l][r] = std:: min(f[l][r], mathsovle(d[l][i], d[i + 1][r], ch[i + 1])),
				f[l][r] = std:: min(f[l][r], mathsovle(f[l][i], f[i + 1][r], ch[i + 1])),
				f[l][r] = std:: min(f[l][r], mathsovle(d[l][i], f[i + 1][r], ch[i + 1])),
				f[l][r] = std:: min(f[l][r], mathsovle(f[l][i], d[i + 1][r], ch[i + 1]));
		}
	int ans = -0x3F3F3F3F;
	for (int i = 1; i <= n; i++) 
		ans = std:: max(ans, d[i][i + n - 1]);
	printf("%d\n", ans);
	for (int i = 1; i <= n; i++)
		if (d[i][i + n - 1] == ans)
			printf("%d ", i);
	printf("\n");
	return 0;
}

P3205 [HNOI2010]合唱队

和上题差不多的套路吧, 需要同时计算从左边插入者的最大值和从右边插入者的最大值, 熟悉了就简单了.
注释写的还算清楚吧(

#include <stdio.h>
#define M114514 19650827
int n;
int a[1003];
int d[1003][1003];	// 最后一个人从左边插入时区间[l,r]的方案个数 
int f[1003][1003];	// 最后一个人从右边插入时区间[l,r]的方案个数 

int main() {
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]), d[i][i] =  1;
	for (int len = 2; len <= n; len++) {
		for (int l = 1; l + len - 1 <= n; l++) {
			int r = l + len - 1;
			
			// 计算 d, 插入者为 l 
			if (a[l] < a[l + 1])	// 前一个插入者为 l + 1
				d[l][r] += d[l + 1][r];
			if (a[l] < a[r])	// 前一个插入者为 r
				d[l][r] += f[l + 1][r];
			d[l][r] %= M114514;
			
			// 计算 f, 插入者为 r 
			if (a[l] < a[r])	// 前一个插入者为 l
				f[l][r] += d[l][r - 1];
			if (a[r - 1] < a[r])	// 前一个插入者为 r - 1
				f[l][r] += f[l][r - 1];
			f[l][r] %= M114514;
		}
	}
	printf("%d\n", (d[1][n] + f[1][n]) % M114514);
	return 0;
} 

P4767 [IOI2000]邮局

首先预处理村庄区间 [l,r] 中只有 1 个邮局时的最小距离, 时间复杂度 \(O(n^2)\)
\(O(mn^2)\) 的算法比较好想, 枚举 i, j, k, 方程为 \(d[i][j]=min(d[i][j],d[k][j-1]+s[k+1][i])\), 其中 \(0<=k<i\)
然后可以用数学推导将原本\(O(mn^2)\)的时间复杂度变为近似\(O(mn)\), 简单说就是缩小了 k 的范围.

#include <stdio.h>
#include <string.h>
#include <algorithm>

int n, m;
int a[3003];
int s[3003][3003];	// 村庄区间 [l,r] 中只有 1 个邮局时的最小距离 
int d[3003][303];	// 前 i 个村庄建设 j 个邮局时的最小距离 
int f[3003][303];	// 满足上一行注释中条件的第 j 个邮局位置 

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	
	memset(d, 0x3F, sizeof(d));
	std:: sort(a + 1, a + n + 1);
	for (int l = 1; l <= n; l++)	// 预处理 s 
		for (int r = l + 1; r <= n; r++) 
			s[l][r] = s[l][r - 1] + a[r] - a[(l + r) / 2];
	
	d[0][0] = 0; 
	for (int j = 1; j <= m; j++) {
		f[n + 1][j] = n;
		for (int i = n; i >= 1; i--) {	// 注意要倒过来 
			int minn = 0x3F3F3F3F, minp = 0;
			for (int k = f[i][j - 1]; k <= f[i + 1][j]; k++) 
				if (d[k][j - 1] + s[k + 1][i] < minn)
					minn = d[k][j - 1] + s[k + 1][i], minp = k;
			d[i][j] = minn;
			f[i][j] = minp;
		}
	}
	
	printf("%d\n", d[n][m]);
	return 0;
}
posted @ 2021-08-10 10:53  dbg_8  阅读(44)  评论(0)    收藏  举报