Loading

2022.10.11 总结

1. 洛谷 P2642

洛谷 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

洛谷 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

洛谷 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

洛谷 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

posted @ 2023-03-02 22:40  chengning0909  阅读(9)  评论(0编辑  收藏  举报