Loading

2022.10.13 总结

1.逐月 P8002

逐月 P8002

题意

\(n\)好的\(a_1, a_2, a_3 \dots a_n\)

一个长度为 \(m\) 的序列是 好的 序列需要满足一下要求:

  1. 对于所有的 \(i\) \((1 \le i < m)\) ,都有 \(b_i < b_{i + 1}\)

  2. 对于所有的 \(i\) \((1 \le i < m)\) ,都有 \(gcd(b_i, b_{i + 1} > 1)\)\(gcd(x, y)\) 表示 \(x\)\(y\) 的最大公因数。

  3. 对于所有的 \(i\) \((1 \le i \le m)\)\(b_i\) 这个数是好的。

请你求出,最长的 好的 序列的长度是多少。

思路

30 分

满足第 \(3\) 条要求很简单,只要选的数都是序列 \(a\) 中的数就可以了。

而满足第 \(1\) 条要求也简单,将数组 \(a\) 排序即可。

那么这个问题就变成了:

找出序列 \(a\) 中的一个子序列,并且满足第 \(2\) 条要求。

那么就可以 搜索 了。

时间复杂度

每个元素有选与不选的两种选择,总共 \(n\) 个元素,\(O(2 ^ n)\)

空间复杂度

原本的数组存储序列 \(a\)\(O(n)\)

临时数组记录序列,\(O(n)\)

空间复杂度 \(O(n)\)

80 分

又是找子序列,又是要最长,所以最长上升子序列模型了解一下?(DP)

时间复杂度

枚举每个元素,\(O(n)\)

枚举前面每个元素,寻找最大长度且满足要求,\(O(n)\)

总时间复杂度为 \(O(n)\)

空间复杂度

一维 \(dp\) 数组记录最长长度,\(O(n)\)

代码

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int q, n, a[N], p[N];

int gcd(int a, int b){
  if (!b) {
    return a;
  }
  return gcd(b, a % b);
}

int main(){
  cin >> q;
  while (q--) {
    cin >> n;
    for (int i = 1; i <= n; i++) {
      cin >> a[i];
    }
    sort(a + 1, a + n + 1);
    int c = -1;
    for (int j = 1; j <= n; j++) {
      p[j] = 1;
      for (int k = 1; k < j; k++) {
        if (gcd(a[j], a[k]) > 1) {
          p[j] = max(p[j], p[k] + 1);
        }
      }
      c = max(c, p[j]);
    }
    cout << c << endl;
    for (int j = 1; j <= n; j++) {  // 初始化
      p[j] = 0;
    }
  }
  return 0;
}

100 分

既然枚举 \(j\) 会超时,那我们就枚举一个不会超时的。

考虑枚举约数,因为既然是要使两个相邻元素之间的最大公约数 \(> 1\),那么它们一定不会互质,也就是有除了 \(1\) 以外的其他公共约数。

而约数总是成对出现的,其中一个 \(\le \sqrt{a_i}\),另一个 \(\ge \sqrt{a_i}\)。所以可以用 \(\sqrt{a_i}\) 的算法去枚举 \(a_i\) 的所有约数。

所以事情又变得简单了,用一个数组 \(g\) 记录最长长度, 也就是 \(g_i\) 表示末尾元素有 \(i\) 这个约数的序列的最长长度。

时间复杂度

枚举每个元素,\(O(n)\)

枚举约数,\(O(\sqrt{a_i})\)

总时间复杂度为 \(O(n \times \sqrt{a_i})\)

空间复杂度

\(g\) 数组记录最长长度,\(O(a_i)\)

代码

int F(){
  for (int i = 1; i < N; i++) {
    g[i] = 0;
  }
  int sum = 0;
  for (int i = 1; i <= n; i++) {
    int ans = 1;
    for (int j = 2; j * j <= a[i]; j++) {   // 枚举约数
      if (a[i] % j == 0) {
        ans = max(ans, max(g[j], g[a[i] / j]) + 1);  // 更新最大值
      }
    }
    sum = max(sum, ans);
    for (int j = 1; j * j <= a[i]; j++) {  // 更新最长长度
      if (a[i] % j == 0) {
        g[j] = max(g[j], ans);
        g[a[i] / j] = max(g[a[i] / j], ans);
      }
    }
  }
  return sum;
}

2. 洛谷 P1510

洛谷 P1510

题意

现在需要体积至少为 \(v\) 的石头,而还剩下 \(n\) 块石头,每块石头的体积为 \(a_i\),搬它的体力值为 \(b_i\)

现在你有 \(c\) 的体力值,请问你能否得到体积为 \(v\) 的石头,如果可以,则输出还能剩下的最多体力值,如果不能,则输出 \(Impossible\)

思路

100 分

这个题目很明显,每个石头只有选和不选两种选择,所以是 \(01\) 背包。

但是这个题目如果开 \(2\) 维数组做的话,空间就炸了,所以,我们需要一些别的方法去优化空间。

在你写 \(01\) 背包的时候,你会发现,第 \(i\) 种状态只和 \(i - 1\) 有关系,和第 \(i - 2 , i - 3 \dots i - i\) 都没有关系,所以,我们只需要记录 \(i\)\(i - 1\),也就是说,只需要一个 \(2 \times c\) 的数组就可以完成了,这个就是 滚动数组

时间复杂度

枚举状态和转移,\(O(n \times c)\)

空间复杂度

滚动数组记录状态,\(O(n)\)

代码

#include <bits/stdc++.h>

using namespace std;

const int N = 10010, C = 10010;

int v, n, c, a[N], b[N], dp[5][C];  // 滚动数组

int main(){
  cin >> v >> n >> c;
  for (int i = 1; i <= n; i++) {
    cin >> a[i] >> b[i];
  }
  int mo = c + 1;
  for (int i = 1; i <= n; i++) {
    for (int j = 0; j <= c; j++) {
      dp[2][j] = dp[1][j];
      if (j >= b[i]) {
        dp[2][j] = max(dp[2][j], dp[1][j - b[i]] + a[i]);
      }
      if (dp[2][j] >= v) {
        mo = min(mo, j);
      }
    }
    for (int j = 0; j <= c; j++) {
      dp[1][j] = dp[2][j];
    }
  }
  if (mo <= c) {
    cout << c - mo;
  } else {
    cout << "Impossible";
  }
  return 0;
}
posted @ 2023-03-02 22:42  chengning0909  阅读(8)  评论(0编辑  收藏  举报