「作业」线性DP
这个作业有点多,尽量一点点地每一道题都写得详细一点吧。
A. 数字三角形 Number Triangles#
传送门:水滴
题目大意:给你一个由正整数组成的三角形,让你选择一条路径,是这条路径的权值和最大。
首先拿到题考虑贪心,但是贪心的做法局限性很大,样例都过不了,否掉。
考虑 dp,发现逆向求解更优,于是观察样例:由每一行往上转移,每次取最大的,最后三角形顶就是答案。
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e4 + 10;
int r;
int f[maxn][maxn];
int main() {
scanf("%d", &r);
for (int i = 1; i <= r; i++) {
for (int j = 1; j <= i; j++) {
scanf("%d", &f[i][j]);
}
}
for (int i = r - 1; i >= 1; i--) {
for (int j = 1; j <= i; j++) {
f[i][j] += max(f[i + 1][j + 1], f[i + 1][j]);
}
}
return printf("%d", f[1][1]), 0;
}
B. 导弹拦截#
传送门:水滴
题目大意:给定一个数列,求它的最长下降子序列和最长上升子序列。
从第一问可知答案是最长上升子序列的长度。第二问其实就是让我们求上升子序列的最少个数。根据 Dilworth 定理可知,第二问其实就是让我们求最长下降子序列的长度。可以很快写出 O(n2) 代码。
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5 + 50;
int n;
int a[maxn];
int f1[maxn], f2[maxn];
int ans = -1;
int res = -1;
int main()
{
int x;
while (cin >> x) a[++n] = x;
f1[1] = 1;
for (int i = 2; i <= n; i++) {
f1[i] = 1;
for (int j = 1; j < i; j++)
if (a[j] >= a[i])
f1[i] = max(f1[i], f1[j] + 1);
ans = max(f1[i], ans);
}
printf("%d\n", ans);
f2[1] = 1;
for (int i = 2; i <= n; i++) {
f2[i] = 1;
for (int j = 1; j < i; j++)
if (a[j] < a[i])
f2[i] = max(f2[i], f2[j] + 1);
res = max(f2[i], res);
}
printf("%d\n", res);
return 0;
}
但是题目的数据范围是 105 的,所以 O(n2) 过不去。考虑优化。(呃先不考虑了)
C. 合唱队形#
传送门:水滴
题目大意:给出 n 个正整数,从中取出若干个数留下 k 个数,使整个数列从左到右从右到左递增到同一个数。求出最小的 n−k。
看完题不难想到分别做两次 dp。先从左到右做一次最长上升子序列,再从右到左做一次最长上升子序列。最后 O(n) 扫一遍数列,相当于枚举那个最中间、最高的那个人,用第一遍的数组和第二遍的数组的第 i 个加起来再减 1(因为 a[i] 出现了两次)就是剩下的人。因为答案要求 n−k 所以还要用 n 减去那个值。
Ps.呃貌似是独立切黄哎(
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e2 + 50;
int n;
int t[maxn];
int f1[maxn], f2[maxn];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &t[i]);
f1[1] = 1;
for (int i = 2; i <= n; i++) {
f1[i] = 1;
for (int j = 1; j < i; j++)
if (t[j] < t[i])
f1[i] = max(f1[i], f1[j] + 1);
}
// cout << f1[4] << '\n';
f2[n] = 1;
for (int i = n; i >= 1; i--) {
f2[i] = 1;
for (int j = n; j > i; j--)
if (t[j] < t[i])
f2[i] = max(f2[i], f2[j] + 1);
}
// cout << f2[4] << '\n';
int ans = 0;
for (int i = 1; i <= n; i++) {
ans = max(ans, f1[i] + f2[i] - 1);
}
printf("%d", n - ans);
return 0;
}
D. 守望者的逃离#
传送门:水滴
题目大意:守望者要逃离一个岛,给出正整数 m,s,t,m 代表守望者的初始法力值,s 表示守望者要逃离的路程,t 表示守望者要在这个时间里逃离。守望者支持以下操作:
-
行走 17m ,耗时 1 秒。
-
使用法术。行走 60m,耗时 1s,消耗 10 点法力。
-
恢复 4 点法力,这时只能原地不动。
显然的,我们尽量让每次操作都是使用法术。所以我们先预处理出只使用法术的 dp 数组,然后再扫一遍,看看直接行走 17m 是否更优即可。中途如果发现已经到达就直接结束循环即可。
点击查看代码
#include <bits/stdc++.h>
#include <ctime>
#define ll long long
using namespace std;
const int maxn = 3e5 + 50;
int m, s, t;
int f[maxn], ans;
int main()
{
scanf("%d%d%d", &m, &s, &t);
for (int i = 1; i <= t; i++) {
if (m >= 10) {
f[i] = f[i - 1] + 60, m -= 10;
} else {
f[i] = f[i - 1], m += 4;
}
}
for (int i = 1; i <= t; i++) {
f[i] = max(f[i], f[i - 1] + 17);
ans = max(ans, f[i]);
if (f[i] > s) {
printf("Yes\n%d", i);
return 0;
}
}
printf("No\n%d", ans);
return 0;
}
E. 乌龟棋#
题目大意:给定 n 个正整数,表示数轴上每个点的权值。给定 m 个正整数,表示有 m 个跳点的次数。问怎样才能让整个数组中获得的权值最大。
考虑 dp。设计状态,f[a][b][c][d] 表示使用 a,b,c,d 个跳的次数获得的最大权值。考虑转移,先遍历每种跳的次数,因为只有放与不放,所以可以写出 a 的转移方程:
f[a][b][c][d]=max(f[a][b][c][d],f[a−1][b][c][d]+q[g])
其他几个也同理。其中 g=1+a+b×2+c×3+d×4。记住 g 一定要加 1,因为遍历时是从 0 开始的。需要注意的是,写状态转移方程式一定要保证当前用的次数要 >0,否则会产生越界。
点击查看代码
#include <bits/stdc++.h>
#include <ctime>
#define ll long long
using namespace std;
const int maxn = 4e2 + 50, maxm = 1e2 + 50, maxf = 50;
int n, m;
int q[maxn], b[maxm];
int f[maxf][maxf][maxf][maxf];
int cnt[5];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d", &q[i]);
for (int i = 1; i <= m; i++) {
scanf("%d", &b[i]);
++cnt[b[i]];
}
f[0][0][0][0] = q[1];
for (int a = 0; a <= cnt[1]; a++)
for (int b = 0; b <= cnt[2]; b++)
for (int c = 0; c <= cnt[3]; c++)
for (int d = 0; d <= cnt[4]; d++) {
int g = 1 + a + b * 2 + c * 3 + d * 4;
if (a) f[a][b][c][d] = max(f[a][b][c][d], f[a - 1][b][c][d] + q[g]);
if (b) f[a][b][c][d] = max(f[a][b][c][d], f[a][b - 1][c][d] + q[g]);
if (c) f[a][b][c][d] = max(f[a][b][c][d], f[a][b][c - 1][d] + q[g]);
if (d) f[a][b][c][d] = max(f[a][b][c][d], f[a][b][c][d - 1] + q[g]);
}
printf("%d\n", f[cnt[1]][cnt[2]][cnt[3]][cnt[4]]);
return 0;
}
摆了八百年,还得继续写作业 /kk
F.饥饿的奶牛#
题目大意:给定 n 个区间,判断在区间没有重复的情况下最多能占多少格。
动态规划。先进行一个简单的设计:f[i] 表示前 i 个格子最多能占多少格。
显然的,f[j]≥f[j−1],因为多选一个格子肯定不会比少选一个少。
最后转移式为:
f[i]=max(f[j−1],f[j−1]+j−(i−1))
最后要使用 vector
来优化空间即可通过此题。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e6 + 50;
inline int read() {
long long x = 0, w = 1;
char ch = getchar ();
for (; ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
return x * w;
}
vector <int> beg[maxn];
int n, mx, f[maxn];
int main()
{
n = read();
for (int i = 1; i <= n; i++) {
int x = read(), y = read();
beg[y].push_back(x - 1);
mx = max(mx, y);
}
for (int i = 1; i <= mx; i++) {
f[i] = f[i - 1];
for (int j = 0; j < beg[i].size(); j++) {
int b = beg[i][j];
f[i] = max(f[i], f[b] + i - b);
}
}
return printf("%d\n", f[mx]), 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!