YBTOJ 5.2区间DP

A.石子合并

image
image

很经典的区间 DP 模板题
我们设 f[l][r] 表示把 [l,r] 这段区间的最小/大得分
考虑枚举 [l,r] 之间的断点 kf[l][r]=max/min(f[l][k]+f[k][r])
但是由于这题是要考虑 我们考虑把原队列复制一份放在末尾
然后枚举一个长度为 n 的定区间

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 201;
int a[N], dpmax[N][N], dpmin[N][N];
int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);

    for (int i = 1; i <= n; i++) a[i + n] = a[i];

    for (int len = 2; len <= n; len++) {
        for (int i = 1; i <= 2 * n + 1 - len; i++) {
            dpmin[i][i + len - 1] = 0x7fffffff;
            for (int k = 0; k < len - 1; k++) {
                dpmax[i][i + len - 1] =
                    max(dpmax[i][i + k] + dpmax[i + k + 1][i + len - 1], dpmax[i][i + len - 1]);
                dpmin[i][i + len - 1] =
                    min(dpmin[i][i + k] + dpmin[i + k + 1][i + len - 1], dpmin[i][i + len - 1]);
            }
            for (int k = i; k <= i + len - 1; k++) {
                dpmax[i][i + len - 1] += a[k];
                dpmin[i][i + len - 1] += a[k];
            }
        }
    }

    int maxn = 0, minn = 0x7fffffff;
    for (int i = 1; i <= 2 * n + 1 - n; i++) {
        maxn = max(maxn, dpmax[i][i + n - 1]);
        minn = min(minn, dpmin[i][i + n - 1]);
    }

    printf("%d\n%d", minn, maxn);

    return 0;
}

B.木板涂色

image
image

因为数据范围比较美丽 并且涂色是一个区间的操作
所以可以考虑一下区间 DP
f[l][r] 表示把 [l,r] 刷到目标状态的最小次数花费
考虑转移 还是有 f[l][r]=min(f[l][k]+f[k][r])
但问题在于 题目给的颜色没用上
发现如果 lr 颜色相同 只需要刷一次
所以有 f[l][r]=min(f[l+1][r],f[l][r1])

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int N = 52;
int f[N][N];
string s;

int main() {
	cin >> s;
	int n = s.length();
	memset(f, 0x3f, sizeof f );
	for (int i = 0; i < n; ++i) f[i][i] = 1;
	for (int len = 2; len <= n; ++len) {
		for (int l = 0; l + len - 1 < n; ++l) {
			int r = l + len - 1;
			if (s[l] == s[r])
				f[l][r] = min(f[l][r - 1], f[l + 1][r]);
			else {
				for (int k = l; k < r; ++k)
					f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r]);
			}
		}
	}
	
	printf("%d",f[0][n - 1]);
	
	return 0;
}

C.消除木块

image
image

头疼
考虑区间 DPf[l][r] 表示把 [l,r] 区间内所有木块都消除的最大得分
但是转移不太好想 单纯枚举 k 的话还是那个问题:颜色是干什么用的
并且假如说我把 r 单独炸掉 那如果 r 后面还有相同颜色的拼在一起 显然就不合法了
那么我们考虑设 f[l][r][x] 表示连接 r 后面还有多少同色木块
那么显然有一个决策是直接把 r 和它连着的那一段全炸掉
或者考虑枚举 [l,r] 之间的 k
但是要有一些特点 比如说...与 r 颜色相同?
那我们把 [k+1,r1] 这段崩掉
rk 就拼在一起了
具体转移可以使用记搜:

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int N = 301;
int f[N][N][N];
int co[N], len[N], a[N];
int T, n;

int jyhss(int l, int r, int k) {
	int all = len[r] + k;
	if (l == r) return all * all;
	if (f[l][r][k] != -1) return f[l][r][k];
	int res = jyhss(l, r - 1, 0) + all * all;
	for (int kkk = l; kkk < r; ++kkk) {
		if (co[kkk] == co[r])
			res = max(res, jyhss(l, kkk, all) + jyhss(kkk + 1, r - 1, 0));
	}
	return f[l][r][k] = res;
}

int main() {
	scanf("%d", &T);
	for (int ii = 1; ii <= T; ++ii) {
		scanf("%d", &n);
		for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
		int lst = -1, cnt = 0;
		for (int i = 1; i <= n; ++i) {
			if (lst != a[i]) {
				++cnt;
				len[cnt] = 1;
				co[cnt] = a[i];
				lst = a[i];
			} else ++len[cnt];
		}
//		for (int i = 1; i <= cnt; ++i) cout<<i<<" "<<co[i]<<" "<<len[i]<<endl;
		memset(f, -1, sizeof f );
		printf("Case %d: %d\n",ii,jyhss(1, cnt, 0));
	}
	
	return 0;
}

D.棋盘分割

image
image

二维区间 DP
式子看起来很吓人 但是考虑转化一下
首先根号是无所谓的 我们让根号里的东西尽可能大就行
除以 n 当然也是无所谓的 因为 n 是定值
进一步观察 发现 x¯ 也是无所谓的 因为它等于全图中所有数的和除以 n
而上面打开就是 i=1nxi2+x¯22x¯xi
它等于 nx¯22x¯i=1nxi+i=1nxi2

其中 i=1nxi 显然还是全图中所有数的和
所以我们就求 i=1nxi2 的最大值
考虑区间 DPf[x1][y1][x2][y2][k] 表示左上角为 (x1,y1) 右下角为 (x2,y2) 大小的矩形被切了 k 刀的 i=1nxi2
然后枚举切的那刀的位置即可

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int N = 20;
int a[10][10];
int f[10][10][10][10][N];
int n;
double avr;

int count(int x1, int y1, int x2, int y2) {
	int sum = 0;
	for (int i = x1; i <= x2; ++i) {
		for (int j = y1; j <= y2; ++j) sum += a[i][j];
	}
	return sum * sum;
}

void getavr(void) {
	int sum = 0;
	for (int i = 1; i <= 8; ++i) {
		for (int j = 1; j <= 8; ++j) sum += a[i][j];
	}
	avr = (double)sum / (n + 1);
}

int main() {
	scanf("%d", &n);
	--n;
	for (int i = 1; i <= 8; ++i) {
		for (int j = 1; j <= 8; ++j) scanf("%d", &a[i][j]);
	}
	getavr();
	memset(f, 0x3f, sizeof f );
	for (int lx = 1; lx <= 8; ++lx) {
		for (int x1 = 1; x1 + lx - 1 <= 8; ++x1) {
			int x2 = x1 + lx - 1;
			for (int ly = 1; ly <= 8; ++ly) {
				for (int y1 = 1; y1 + ly - 1 <= 8; ++y1) {
					int y2 = y1 + ly - 1;
					f[x1][y1][x2][y2][0] = count(x1, y1, x2, y2);
				}
			}
		}
	}
	
	for (int i = 1; i <= n; ++i) {
		for (int lx = 1; lx <= 8; ++lx) {
			for (int x1 = 1; x1 + lx - 1 <= 8; ++x1) {
				int x2 = x1 + lx - 1;
				for (int ly = 1; ly <= 8; ++ly) {
					for (int y1 = 1; y1 + ly - 1 <= 8; ++y1) {
					int y2 = y1 + ly - 1;
					for (int kx = x1; kx < x2; ++kx) {
						f[x1][y1][x2][y2][i] = min(	f[x1][y1][x2][y2][i], f[x1][y1][kx][y2][0] + f[kx + 1][y1][x2][y2][i - 1]);
						f[x1][y1][x2][y2][i] = min(	f[x1][y1][x2][y2][i], f[x1][y1][kx][y2][i - 1] + f[kx + 1][y1][x2][y2][0]);
					}
					for (int ky = y1; ky < y2; ++ky) {
						f[x1][y1][x2][y2][i] = min(f[x1][y1][x2][y2][i], f[x1][y1][x2][ky][0] + f[x1][ky + 1][x2][y2][i - 1]);
						f[x1][y1][x2][y2][i] = min(f[x1][y1][x2][y2][i], f[x1][y1][x2][ky][i - 1] + f[x1][ky + 1][x2][y2][0]);
					}
				}
				}
			}
		}
	}
	
	double ans = (double)f[1][1][8][8][n];
	ans = ans / (n + 1);
	printf("%.3lf",sqrt(ans - avr * avr));
	
	return 0;
}

E.删数问题

image
image

fl,r 表示把 [l,r] 这段区间的数全删掉的最大收益
那我们就有两种选择:

  1. 一次操作把整个区间都删掉
  2. 枚举断点 k 拆成 fl,k+fk+1,r

然后直接转移即可

  • 为什么这样操作一定是合法的

考虑最后构成答案的操作一定为 [x1,x2],[x2,x3],...,[xs1,xs] 这若干个连续区间

那么我们按顺序删 一定可以保证删除操作合法

点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

namespace steven24 {
	
const int N = 105;	
int a[N];
ll f[N][N];
int n;

ll calc(int l, int r) {
	if (l == r) return 1ll * a[l];
	else return 1ll * abs(a[l] - a[r]) * (r - l + 1);
}

void main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = 1; i <= n; ++i) f[i][i] = a[i];
	for (int len = 2; len <= n; ++len) {
		for (int l = 1; l + len - 1 <= n; ++l) {
			int r = l + len - 1;
			f[l][r] = calc(l, r);
			for (int k = l; k < r; ++k) f[l][r] = max(f[l][r], f[l][k] + f[k + 1][r]);
		}
	}
	printf("%lld\n", f[1][n]);
}
	
}

int main() {
	steven24::main();
	return 0;
} 
/*
6
54 29 196 21 133 118
*/

F.恐狼后卫

image
image

有个比较直觉的想法:如果要对一只狼开刀 最好的选择一定是直接砍到死

考虑证明

首先我们要对一只狼砍的刀数是一定的 也就是说我们承受伤害的次数是一定的
并且该狼的攻击力一定 所以有影响的一定是它左右两只狼

假设我承受 bx1,by1 的攻击力砍了若干刀 bx2,by2 的攻击力砍了若干刀...等等直到把它砍死
那么也就说明我可以通过一些方式使任何 (xi,yi) 都取的到
那么我们选取其中代价最小的一组 把这只狼直接从满血砍到死 一定是小于等于上述操作的代价的

然后考虑如何区间 DP
fl,r 表示把 [l+1,r1] 的狼全砍死需要的最小代价
转移的时候枚举断点 kk 从满血砍到死
那么我们就要先把 [l+1,k1][k+1,r1] 这两个区间的狼全砍死
然后承受 bl,br 的代价

点击查看代码
#include <bits/stdc++.h>
#define ll long long

using namespace std;

const int N = 520;
const ll inf = 0x7ffffffffffffff;
ll f[N][N];
int a[N], b[N], hp[N];
int n, atk;

int get(int x) {
    if (x < atk) return 1;
    int ret = x / atk;
    if (x % atk != 0) ++ret;
    return ret;
}

int main() {
    scanf("%d%d", &n, &atk);
    for (int i = 1; i <= n; ++i) scanf("%d%d%d", &a[i], &b[i], &hp[i]);

    for (int len = 3; len <= n + 2; ++len) {
        for (int l = 0; l + len - 1 <= n + 1; ++l) {
            int r = l + len - 1;
            f[l][r] = inf;
            for (int k = l + 1; k <= r - 1; ++k) f[l][r] = min(f[l][r], f[l][k] + get(hp[k]) * (a[k] + b[l] + b[r]) + f[k][r]);
        }
    }

    printf("%lld",f[0][n + 1]);

    return 0;
}

G.矩阵取数

image
image

观察发现每一行之间相互不影响 所以我们可以每一行都分别做一次 然后累加答案

fl,r 表示 [l,r] 这段区间的得分 那么显然有 fl,r=max(fl+1,r+costl,fl,r1+costr)

那么问题的关键就是求出当前拿走这个数的得分

进一步观察可以发现 因为是 m 次取完 所以如果区间长度已知那么当前操作是第几次取数是一定的

稍微推一下:
总长为 m
区间长为 m :第一次取数
区间长为 m1 :第二次取数
区间长为 1 :第 m 次取数
区间长为 len :第 mlen+1 次取数

答案要开高精 偷懒直接 __int128 也行

点击查看代码
#include <bits/stdc++.h>
#define int __int128
using namespace std;

namespace steven24 {
	
int f[101][101];	
int base[101];
int a[101][101];
int n, m;
int ans;
	
inline int read() {
    int xr = 0, F = 1; 
	char cr;
    while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
    while (cr >= '0' && cr <= '9') 
        xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
    return xr * F;
}

inline void write(int x) {
	if (x < 0) putchar('-'), x = -x;
	if (x >= 10) write(x / 10);
	putchar(x % 10 + '0');
}

void init() {
	base[0] = 1;
	for (int i = 1; i <= m; ++i) base[i] = base[i - 1] * 2;
}

void main() {
	n = read(), m = read();
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) a[i][j] = read();
	}
	init();
	
	for (int s = 1; s <= n; ++s) {
		for (int i = 1; i <= m; ++i) f[i][i] = base[m] * a[s][i];
		for (int len = 2; len <= m; ++len) {
			for (int l = 1; l + len - 1 <= m; ++l) {
				int r = l + len - 1;
				f[l][r] = max(f[l + 1][r] + a[s][l] * base[m - len + 1], f[l][r - 1] + a[s][r] * base[m - len + 1]);
//				write(l), putchar(' '), write(r), putchar(' '), write(f[l][r]), putchar('\n');
			}
		}
		ans += f[1][m];
	}
	write(ans);
}
	
}

signed main() {
	steven24::main();
	return 0;
}
/*
2 3
1 2 3
3 4 2
*/

H.生日欢唱

image
image

其实感觉不太像区间 DP

fl,r 表示强制使男生第 l 个和女生第 r 个配对的最大价值
枚举 l,r 前面的 i,j 则有 fl,r=fi,jcosti+1,l1costj+1,r1+vall,r
其中 costl,r 表示把 [l,r] 这段区间的学生全删掉的代价

但是这样转移是 O(n4)

通过进一步思考性质 我们可以发现 如果两个前面都丢掉了人 一定是亏的
所以直接枚举 l 这边丢掉多少人和 r 这边丢掉多少人即可
查询代价需要加个前缀和预处理
这样就是 O(n3)

对于答案 我们直接在每个 fl,r 转移出来之后 算出把剩下学生都丢掉的代价减掉 然后与答案取 max 即可

注意初值问题

点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

namespace steven24 {
	
const int N = 521;
ll f[N][N];
int a[2][N];
int d[2][N];
int n;
ll ans;

void init() {
	for (int j = 0; j <= 1; ++j) {
		for (int i = 1; i <= n; ++i) d[j][i] = d[j][i - 1] + a[j][i];
	}
}

ll calc(int l, int r, int opt) {
	if (l > r) return 0;
	int sum = d[opt][r] - d[opt][l - 1];
	return 1ll * sum * sum;
}

void main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[0][i]);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[1][i]);
	init();
	
	memset(f, -0x3f, sizeof f);
	f[0][0] = 0;
	
	for (int l = 1; l <= n; ++l) {
		for (int r = 1; r <= n; ++r) {
			for (int k = 0; k < l; ++k) f[l][r] = max(f[l][r], f[k][r - 1] - calc(k + 1, l - 1, 0) + 1ll * a[0][l] * a[1][r]);
 			for (int k = 0; k < r; ++k) f[l][r] = max(f[l][r], f[l - 1][k] - calc(k + 1, r - 1, 1) + 1ll * a[0][l] * a[1][r]);
			ans = max(ans, f[l][r] - calc(l + 1, n, 0) - calc(r + 1, n, 1));
		}
	}
	
	printf("%lld\n", ans);
}
	
}

int main() {
	steven24::main();
	return 0;
}
/*
3
1 1 5
5 1 1
*/

I.最小代价

image
image

DAY1

dpl,r 表示把 [l,r] 这段区间都消掉的代价
fl,r,x,y 表示把 [l,r] 这段区间内除了值域在 [x,y] 的所有数都消掉的代价
那么显然有 dpl,r=min(fl,r,x,y+A+B×(yx)2)

然后我们考虑 f 数组的转移
考虑枚举断点 有
fl,r,x,y=min(dpl,k+fk+1,r,x,y)
fl,r,x,y=min(fl,k,x,y+dpk+1,r)
fl,r,x,y=min(fl,k,x,y+fk+1,r,x,y)

对于初值 有
dpi,i=A
fi,i,1~ai,ai~maxn=0

结合上面的定义应该不难理解

此题需要离散化

点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

namespace steven24 {
	
const int N = 52;
ll f[N][N][N][N], dp[N][N];
int a[N], b[N];
int n, A, B, tot;
ll ans = 0x7fffffff;

void discretization() {
	memcpy(b, a, sizeof a);
	sort(b + 1, b + 1 + n);
	tot = unique(b + 1, b + 1 + n) - b - 1;
	for (int i = 1; i <= n; ++i) a[i] = lower_bound(b + 1, b + 1 + tot, a[i]) - b;
} 

void dfs(int l, int r) {
	if (l > r) return;
	if (dp[l][r] != 0x3f3f3f3f3f3f3f3f) return;
	for (int x = 1; x <= tot; ++x) {
		for (int y = x; y <= tot; ++y) {
			for (int k = l; k < r; ++k) {
				dfs(l, k);
				dfs(k + 1, r);
				f[l][r][x][y] = min(f[l][r][x][y], dp[l][k] + f[k + 1][r][x][y]);
				f[l][r][x][y] = min(f[l][r][x][y], f[l][k][x][y] + dp[k + 1][r]);
				f[l][r][x][y] = min(f[l][r][x][y], f[l][k][x][y] + f[k + 1][r][x][y]);
				dp[l][r] = min(dp[l][r], f[l][r][x][y] + A + B * (b[y] - b[x]) * (b[y] - b[x]));
			}
		}
	}
}

void main() {
	scanf("%d%d%d", &n, &A, &B);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	discretization(); 
	
	memset(dp, 0x3f, sizeof dp);
	memset(f, 0x3f, sizeof f); 
	for (int i = 1; i <= n; ++i) dp[i][i] = A;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= a[i]; ++j) {
			for (int k = a[i]; k <= tot; ++k) f[i][i][j][k] = 0;
		}
	}
	
	dfs(1, n);
	
	printf("%lld\n", dp[1][n]);
}	
	
}

int main() {
	steven24::main();
	return 0;
} 
/*
10
3 1
7 10 9 10 6 7 10 7 1 2
*/
posted @   Steven24  阅读(69)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示