2022.10.11 总结
1. 洛谷 P2642
题意
给定一个长度为 \(n\) 的序列,从中选出两段字段,使得两段字段的和最大。
两段字段的长度最少为 \(1\),且之间至少间隔一个数。
请你求出最大和。
思路
60 分
从头到尾算一遍最大子段和,再从后往前算一遍最大子段和。
然后枚举每一个数,再枚举后面的所有最大子段和,求出它们和的最大值即可。
时间复杂度
算最大子段和,\(O(n)\)。
求最大值,\(O(n ^ 2)\)。
总时间复杂度为 \(O(n ^ 2)\)。
空间复杂度
两个一维 \(dp\) 数组求最大子段和,\(O(n)\)。
代码
for (int i = 1; i <= n; i++) { // 从前往后
cin >> a[i];
dp1[i] = max(0ll, dp1[i - 1]) + a[i]; // 求最大子段和
}
for (int i = n; i >= 1; i--) { // 从后往前
dp2[i] = max(0ll, dp2[i + 1]) + a[i];
}
for (int i = 1; i <= n; i++) {
for (int j = i + 2; j <= n; j++) { // 枚举另一个最大子段和
ans = max(ans, dp1[i] + dp2[j]); // 更新最大值
}
}
100 分
从头到尾算一遍最大子段和,再从后往前算一遍最大子段和。
然后每次维护从前往后的前缀最大值,再与当前从后往前的最大子段和相加,更新最大值即可。
时间复杂度
求最大子段和,\(O(n)\)。
求最大值,\(O(n)\)。
总时间复杂度为 \(O(n)\)。
空间复杂度
两个一维 \(dp\) 数组记录最大子段和,\(O(n)\)。
代码
for (int i = 1; i <= n; i++) {
cin >> a[i];
dp1[i] = max(0ll, dp1[i - 1]) + a[i];
}
for (int i = n; i >= 1; i--) {
dp2[i] = max(0ll, dp2[i + 1]) + a[i];
}
long long p = dp1[1];
for (int i = 3; i <= n; i++) {
p = max(p, dp1[i - 2]); // 维护前缀最大值
ans = max(ans, dp2[i] + p); // 更新答案最大值
}
2. 洛谷 P1095
题意
守望者的跑步速度为 \(17 m/s\),他可以使用 \(10\) 点的魔法值使自己在这一秒移动 \(60 m\),当他待在原地不动时,可以 \(1\) 秒恢复 \(4\) 点的魔法值。
守望者现在有 \(M\) 点魔法值,他要在 \(T\) 秒内跑出 \(S\) 米,请你求出他能否完成任务,如果可以,输出最小时间,如果没有,输出能跑的最远距离。
思路
100 分(DP)
如果能用魔法,就尽量用魔法,但是,有时候,用了魔法反而没有跑步快,所以先不记录跑步的距离,只记录使用魔法和恢复魔法值在每一秒能跑的最长距离,再对每秒进行判断是跑步跑得远还是用魔法能跑更远。
时间复杂度
处理只使用魔法,\(O(t)\)。
判断跑步还是用魔法,\(O(t)\)。
总时间复杂度为 \(O(t)\)。
空间复杂度
记录每秒的跑步最大值,\(O(t)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int T = 3e5 + 10;
int m, s, t, dp[T];
int main(){
cin >> m >> s >> t;
int p = m;
for (int i = 1; i <= t; i++) {
if (m >= 10) {
m -= 10, dp[i] = dp[i - 1] + 60; // 用魔法
} else {
m += 4, dp[i] = dp[i - 1]; // 恢复魔法值
}
}
for (int i = 1; i <= t; i++) {
dp[i] = max(dp[i], dp[i - 1] + 17); // 取最优值
}
if (dp[t] < s) {
cout << "No\n" << dp[t];
} else {
int i = 1;
for ( ; i <= t; i++) { // 找出最短时间
if (dp[i] >= s) {
break;
}
}
cout << "Yes\n" << i;
}
return 0;
}
100 分(贪心)
如果能用魔法,就直接用魔法。
否则,看还需要多少秒可以再次使用魔法,判断在这段时间内,跑步还是恢复魔法更优。
时间复杂度
最多只能循环 \(t\) 次,\(O(t)\)。
空间复杂度
在不能使用魔法时,需要一个临时变量记录还要多少秒会发才能使用魔法,\(O(1)\)。
代码
#include <bits/stdc++.h>
using namespace std;
int m, s, t, ti, si;
int main(){
cin >> m >> s >> t;
while (si <= s && ti < t) {
if (m >= 10) { // 能用魔法
si += 60, ti++, m -= 10;
} else {
int y = ceil((10 - m % 10) / 4.0);
if (ti + y < t && si + (y + 1) * 17 < s) { // 恢复魔法值更优
ti++, m += 4;
} else { // 跑步更优
si += 17, ti++;
}
}
}
if (si >= s && ti <= t) {
cout << "Yes\n" << ti;
} else {
cout << "No\n" << si;
}
return 0;
}
3. 洛谷 P1719
题意
有一个 \(n \times n\) 的矩阵,请你求出在矩阵中选择一个和最大的子矩阵,它的和是多少。
思路
100 分(前缀和)
记录二维前缀和,暴力枚举左上角和右下角,求出最大的和。
时间复杂度
预处理前缀和,\(O(n ^ 2)\)。
枚举左上角和右下角,\(O(n ^ 4)\)。
总时间复杂度为 \(O(n ^ 4)\)。
空间复杂度
记录前缀和,\(O(n ^ 2)\)。
代码
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> a[i][j];
dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1] + a[i][j]; // 记录前缀和
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
for (int k = i; k <= n; k++) {
for (int l = j; l <= n; l++) {
ans = max(ans, dp[k][l] - dp[i - 1][l] - dp[k][j - 1] + dp[i - 1][j - 1]);
}
}
}
}
100 分(DP)
每次枚举两个列坐标,再求出行的最大子段和。
时间复杂度
枚举两个列坐标,\(O(n ^ 2)\)。
求出最大子段和,\(O(n)\)。
总时间复杂度为 \(O(n ^ 3)\)。
空间复杂度
记录前缀和,\(O(n ^ 2)\)。
记录最大子段和,\(O(n)\)。
总空间复杂度为 \(O(n ^ 2)\)。
代码
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> a[i][j];
s[i][j] = s[i][j - 1] + a[i][j]; // 记录前缀和
}
}
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
for (int k = 1; k <= n; k++) {
dp[k] = max(dp[k - 1], 0) + s[k][j] - s[k][i - 1]; // 求最大子段和
ans = max(ans, dp[k]);
}
}
}
4. 洛谷 P5017
题意
有一辆摆渡车,它从 \(A\) 点出发,前往 \(B\) 点,每走一个来回需要 \(m\) 分钟。
有 \(n\) 个人, 第 \(i\) 个人会在时刻 \(t_i\) 到达 \(A\) 点,摆渡车需要将他们全部送到 \(B\) 点。
现在可以任意安排摆渡车的出发时间,请问这些人的等车时间的和最小是多少。
思路
30 分
枚举发车时间,记录等待时间最小值。
时间复杂度
枚举时间,\(O(\max \{ t_i\} + m)\)。
记录最小值,\(O(n ^ 2)\)。
总时间复杂度为 \(O(T \times n ^ 2)\)。
空间复杂度
一维 \(dp\) 数组记录最小值,\(O(T)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 510, T = 4e6 + 10;
int n, m, t[N];
long long dp[T], sum = LLONG_MAX;
long long C(int x, int y){ // 枚举区间内的时间
long long ans = 0;
for (int i = 1; i <= n; i++) {
if (x <= t[i] && t[i] <= y) {
ans += y - t[i];
}
}
return ans;
}
int main(){
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> t[i];
}
sort(t + 1, t + n + 1); // 按时间先后排序
for (int i = 0; i < t[n] + m; i++) {
dp[i] = C(0, i);
if (i >= m) {
for (int j = 0; j <= i - m; j++) {
dp[i] = min(dp[i], dp[j] + C(j + 1, i)); // 取最小值
}
}
if (i >= t[n]) { =
sum = min(sum, dp[i]);
}
}
cout << sum;
return 0;
}
50 分
和 30 分想法一样,但是 \(C\) 函数时间复杂度过大,可以考虑用前缀和进行优化。
\(C(x, y) = \sum \limits _ {x \le a_i \le y} ^ {n} {(y - a_i)} = \sum \limits _ {x \le a_i \le y} ^ {n} {y} - \sum \limits _ {x \le a_i \le y} ^ {n} {a_i}\)
所以我们需要两个数组,分别记录在时刻 \(i\) 时来的人数和前 \(i\) 个 \(t_i\) 的和。
时间复杂度
枚举时间,\(O(\max \{ t_i\} + m)\)。
记录最小值,\(O(n)\)。
总时间复杂度为 \(O(T \times n)\)。
空间复杂度
一维 \(dp\) 数组记录最小值,\(O(T)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 510, T = 4e6 + 10;
int n, m, t[N];
long long dp[T], sum = LLONG_MAX, s[T], cnt[T];
long long C(int x, int y){ // O(1) 查询
if (!x) {
return cnt[y] * y - s[y];
} else {
return (cnt[y] - cnt[x - 1]) * y - (s[y] - s[x - 1]);
}
}
int main(){
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> t[i];
cnt[t[i]]++, s[t[i]] += t[i];
}
sort(t + 1, t + n + 1);
for (int i = 1; i < t[n] + m; i++) { // 记录前缀和
cnt[i] += cnt[i - 1], s[i] += s[i - 1];
}
for (int i = 0; i < t[n] + m; i++) {
dp[i] = C(0, i);
if (i >= m) {
for (int j = 0; j <= i - m; j++) {
dp[i] = min(dp[i], dp[j] + C(j + 1, i));
}
}
if (i >= t[n]) {
sum = min(sum, dp[i]);
}
}
cout << sum;
return 0;
}
70 分
保留 50 分的优化,并添加一个新的优化。
如果时刻 \(i\) 发车,那么上一次发车一定是在 \([i - 2 \times m + 1, i - m]\) 之间。
如果上次发车在 \(i - 2 \times m\) 时,那么 \(i - m\) 时还可以发一次车,很明显,发车越频繁,等待时间越小,所以上次发车时间一定在 \([i - 2 \times m + 1, i - m]\) 之间。
时间复杂度
枚举时刻,\(O(T)\)。
枚举上次发车时间,\(O(m)\)。
总时间复杂度为 \(O(T \times m)\)。
空间复杂度
一维 \(dp\) 数组记录最小值,\(O(T)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 510, T = 4e6 + 10;
int n, m, t[N];
long long dp[T], sum = LLONG_MAX, s[T], cnt[T];
long long C(int x, int y){
if (!x) {
return cnt[y] * y - s[y];
} else {
return (cnt[y] - cnt[x - 1]) * y - (s[y] - s[x - 1]);
}
}
int main(){
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> t[i];
cnt[t[i]]++, s[t[i]] += t[i];
}
sort(t + 1, t + n + 1);
for (int i = 1; i < t[n] + m; i++) {
cnt[i] += cnt[i - 1], s[i] += s[i - 1];
}
for (int i = 0; i < t[n] + m; i++) {
dp[i] = C(0, i);
if (i >= m) {
for (int j = max(0, i - 2 * m + 1); j <= i - m; j++) { // 枚举上次发车时间
dp[i] = min(dp[i], dp[j] + C(j + 1, i));
}
}
if (i >= t[n]) {
sum = min(sum, dp[i]);
}
}
cout << sum;
return 0;
}
加 \(O(2)\) 优化可以 AC