Loading

2022.10.12 总结

1. 洛谷 P1435

洛谷 P1435

题意

给定一个字符串 \(s\),请你求出最少要插入多少个字符能使 \(s\) 变为回文串。

思路

30 分

要使 \(s\) 变为回文串,就是要使 \(s_1 = s_n,s_2 = s_{n - 1} \dots\)

所以每次只要保证最两边的字符相同即可。

\(f(l, r)\) 表示使 \(s_l \sim s_r\) 变成回文串需要插入的最少字符数量。

  • 如果 \(s_l == s_r\),则只需要考虑 \(s_{l + 1} \sim s_{r - 1}\) 是否为回文串即可。\(f(l, r) = f(l + 1, r - 1)\)

  • 如果 \(s_l \ne s_r\),则有两种情况:一种是让下一个回文的字符为 \(s_l\),另一种则是让下一个回文的字符为 \(s_r\)\(f(l, r) = \max \{f(l + 1, r), f(l, r - 1)\} + 1\),自己需要插入一次。

所以,可以直接暴力。

时间复杂度

\(O(2 ^ n)\sim O(3 ^ n)\),指数级别(太慢了)。

空间复杂度

字符串长度为 \(n\)\(O(n)\)

代码

int f(int l, int r){
  if (l >= r) {  // 递归边界
    return 0;
  }
  if (s[l] == s[r]) {
    return f(l + 1, r - 1);
  }
  return min(f(l + 1, r), f(l, r - 1)) + 1;
}

100 分(记忆化搜索)

和 30 分差不多(时间复杂度特别优秀,比 DP 要慢一点)

时间复杂度

每个状态只遍历一次,\(O(n ^ 2)\)

空间复杂度

数组记录状态,\(O(n ^ 2)\)

代码

int f(int l, int r){
  if (l >= r) {
    return 0;
  }
  if (dp[l][r] != -1) {  // 记忆化
    return dp[l][r];
  }
  if (s[l] == s[r]) {
    return dp[l][r] = f(l + 1, r - 1);
  }
  return dp[l][r] = min(f(l + 1, r), f(l, r - 1)) + 1;
}

100 分(DP)

拓扑序是区间长度从小到大,所以先枚举区间长度。

时间复杂度

先枚举区间长度,\(O(n)\)

枚举每种长度的所有区间,\(O(n)\)

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

空间复杂度

记录每种状态,\(O(n ^ 2)\)

代码

for (int i = 0; i < n - 1; i++) {
  dp[i][i + 1] = s[i] != s[i + 1];   // 初始状态
}
for (int l = 3; l <= n; l++) {
  for (int i = 0, j = l - 1; j < n; i++, j++) {  // 转移
    if (s[i] == s[j]) {
      dp[i][j] = dp[i + 1][j - 1];
    } else {
      dp[i][j] = min(dp[i + 1][j], dp[i][j - 1]) + 1;
    }
  }
}

2. 洛谷 P3146

洛谷 P3146

题意

给定一个序列 \(a\),每次可以选择相邻两个相同的元素 \(a_i, a_{i + 1}\) 合并,合并后只剩下一个元素,值为合并前的元素 \(+1\)

请你求出序列中最大的元素的值。

思路

100 分

每次都是选择两个相邻的元素合并,所以序列中每个元素要么是本来就存在序列中的,要么是由连续的一段子段组成的,所以可以根据这个性质做一些事情。

\(dp_{i, j}\)\(a_i \sim a_j\) 的这一段区间最终合并成的元素,如果不可以合并为一个元素,则 \(dp_{i, j} = 0\)

所以事情就变得很简单了。

时间复杂度

枚举区间长度,\(O(n)\)

枚举每个区间,\(O(n)\)

枚举断点,\(O(n)\)

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

空间复杂度

记录每个状态,\(O(n ^ 2)\)

代码

#include <bits/stdc++.h>

using namespace std;

const int N = 310;

int n, a[N], dp[N][N], ans;

int main(){
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
    dp[i][i] = a[i];
  }
  for (int len = 2; len <= n; len++) {  // 枚举长度
    for (int i = 1, j = len; j <= n; i++, j++) {  // 枚举区间
      for (int k = i; k < j; k++) {
        if (dp[i][k] && dp[i][k] == dp[k + 1][j]) {
          dp[i][j] = max(dp[i][j], dp[i][k] + 1);
        }
      }
      ans = max(dp[i][j], ans);
    }
  }
  cout << ans;
  return 0;
}

3. 洛谷 P1063

洛谷 P1063

题意

有一条项链,上面有 \(N\) 颗珠子,每颗珠子都有头标记和尾标记 \(a_i, b_i\),每颗珠子的尾标记和它的下一颗珠子的头标记是一定相等的。

每次操作可以选择两颗相邻的珠子 \(i, i+1\),将它们变为一颗珠子,那颗新的珠子的头标记为 \(a_i\),尾标记为 \(b_{i + 1}\),释放的能量为 \(a_i \times b_i \times b_{i + 1}\)

请你求出在只剩下一颗珠子时,最多能释放多少能量。

思路

因为这条项链是一个环,不好处理,所以有以下两种方法处理这个环。

100 分(断环成链)

将这个环从某一个点 \(i\) 断开,变成一条链状,然后做区间 DP,更新最大能量。

时间复杂度

枚举断点,\(O(n)\)

区间 DP,\(O(n ^ 3)\)

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

空间复杂度

记录每个状态,\(O(n ^ 2)\)

代码

#include <bits/stdc++.h>

using namespace std;

const int N = 310;

int n, a[N], dp[N][N], ans, b[N];

int main(){
  cin >> n;
  for (int i = 0; i < n; i++) {
    cin >> a[i];
  }
  for (int x = 0; x < n; x++) {  // 枚举断点
    for (int y = 0; y < n; y++) {  
      b[y] = a[(x + y) % n];
    }
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
        dp[i][j] = 0;
      }
    }
    // 区间 DP
    for (int len = 2; len <= n; len++) {
      for (int i = 0, j = len - 1; j < n; i++, j++) {
        for (int k = i; k < j; k++) {
          dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j] + b[i] * b[k + 1] * b[(j + 1) % n]);
        }
      }
    }
    ans = max(ans, dp[0][n - 1]);
  }
  cout << ans;
  return 0;
}

100 分(将环拆成两倍长度的链)

将这个环变成两倍长度的链。

\([1, 3, 2, 4]\) -> \([1, 3, 2, 4, 1, 3, 2, 4]\)

对这个序列做区间 DP。

时间复杂度

枚举区间长度 \(len\)\(O(n)\)

枚举 \([0, 2 \times n]\) 的所有长度为 \(len\) 的区间,\(O(2 \times n)\)

枚举区间断点,\(O(n)\)

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

空间复杂度

记录每个状态,\(O(n ^ 2)\)

代码

#include <bits/stdc++.h>

using namespace std;

const int N = 310;

int n, a[N], dp[N][N], ans;

int main(){
  cin >> n;
  for (int i = 0; i < n; i++) {
    cin >> a[i];
    a[i + n] = a[i];  // 将环变为两倍长度的链
  }
  // 区间 DP
  for (int len = 2; len <= n; len++) {
    for (int i = 0, j = len - 1; j < 2 * n; i++, j++) {
      for (int k = i; k < j; k++) {
        dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j] + a[i] * a[k + 1] * a[j + 1]);
      }
    }
  }
  for (int i = 0; i < n; i++) {
    ans = max(ans, dp[i][i + n - 1]);
  }
  cout << ans;
  return 0;
}
posted @ 2023-03-02 22:41  chengning0909  阅读(12)  评论(0编辑  收藏  举报