补题链接:Here

A - Rainy Season

如果不是 RSR 型的话直接计算 R 的数量即可

B - Making Triangle

给定 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图1 根长度分别为 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图2 的棍子,问能组成多少个三边长度各不相同的三角形?如果两个三角形至少用了一根不同编号的棍子,则称它们是不同的三角形。

由于数据范文较小 (AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图3),所以我们可以排序以后枚举三元组即可。

另外 CP wiki 提到这里进一步优化的话,可以在固定最长边的基础上,用双指针确定另外两条边的长度范围,这样时间复杂度就降到的了 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图4#card=math&code=%5Cmathcal%7BO%7D%28N%5E2%29)。

C - Walking Takahashi

题意:有一个数 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图5 ,对它进行 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图6AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图7AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图8 的操作,求操作后的 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图9

思路:

首先XX的正负不影响结果,所以我们可以只考虑 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图10

如果 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图11,那么我们首先应该向原点移动,直到 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图12。这时还剩下 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图13 次操作,我们应当在原点的左右两侧来回移动。根据 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图14 的奇偶判断一下最后在哪一个位置即可。

  1. using ll = long long;
  2. int main() {
  3. ios_base::sync_with_stdio(false), cin.tie(0);
  4. ll X, K, D, R;
  5. cin >> X >> K >> D;
  6. if (X < 0) X = -X;
  7. R = X / D;
  8. if (K < R) {
  9. cout << (X - K * D);
  10. return 0;
  11. }
  12. K -= R, X -= R * D;
  13. cout << (K & 1 ? D - X : X);
  14. return 0;
  15. }

D - Moving Piece

AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图15个方格,从第 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图16 个方格会跳到第 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图17 个方格。PP是 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图18的一个排列。

每个方格上写了一个数字 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图19 。每次跳跃时,会得到等同于 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图20 的分数。你可以从任意方格开始,跳跃至少一次,至多 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图21 次,求能够取得的最高分数。

思路:枚举起点。由于 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图22 是排列,所以我们从任意位置 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图23 开始,经过若干次跳跃后一定会回到 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图24 。我们可以计算出一个周期内的前缀和。然后,根据周期长度 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图25AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图26 之间的关系,分情况讨论。

  • AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图27,此时我们应该选择前 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图28 个前缀和中的最大值。

  • AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图29,令AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图30,则我们可以选择

    • 不循环,选择所有前缀和中的最大值。
    • 循环 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图31 次,再加上前 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图32 个前缀和中的最大值。
    • 循环 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图33 次,再加上所有前缀和中的最大值。
  • AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图34#card=math&code=%5Cmathcal%7BO%7D%28N%5E2%29)
  1. // Murabito-B 21/04/08
  2. #include <bits/stdc++.h>
  3. using ll = long long;
  4. using namespace std;
  5. int main() {
  6. ios_base::sync_with_stdio(false), cin.tie(0);
  7. int n, k;
  8. cin >> n >> k;
  9. vector<int> p(n), c(n);
  10. for (int i = 0; i < n; ++i) cin >> p[i];
  11. for (int i = 0; i < n; ++i) cin >> c[i];
  12. ll ans = LLONG_MIN; // long long的最小值
  13. for (int i = 0; i < n; ++i) {
  14. vector<bool> book(n);
  15. int idx = i;
  16. vector<ll> sum = {0}, hi = {LLONG_MIN};
  17. while (!book[p[idx] - 1]) {
  18. idx = p[idx] - 1;
  19. book[idx] = true;
  20. sum.emplace_back(sum.back() + c[idx]);
  21. hi.emplace_back(max(hi.back(), sum.back()));
  22. }
  23. int m = sum.size() - 1;
  24. int f = k / m, res = k % m;
  25. ll result = 0;
  26. if (f > 0) result = max(hi[m], max(sum[m] * f + (res == 0 ? 0 : hi[res]), sum[m] * (f - 1) + hi[m]));
  27. else
  28. result = hi[res];
  29. ans = max(ans, result);
  30. }
  31. cout << ans << "\n";
  32. return 0;
  33. }

另外如果 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图35 呢?应该如何改进算法?

这里想了很久,只想到了 RMQ解决但代码部分没写出来,只能转载一下 CP wiki 的了

提示一:

在上面的算法中,对于一个循环,设其长度为 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图36 ,我们实际上重复计算了 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图37 次(针对每一个起点)。有没有可能减少这样的重复计算呢?

提示二

在每一个循环内,问题实际上可以转化为,给定一个由 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图38 个数围成的圈,从中取出长度不超过AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图39的一段连续串,求能取得的最大和。

提示三

前缀和+RMQ。

  1. // Murabito-B 21/04/08
  2. #include <bits/stdc++.h>
  3. using ll = long long;
  4. #define MAXN 5005
  5. #define K 15
  6. using namespace std;
  7. const ll LO = -1e16;
  8. int n, k;
  9. ll st[MAXN * 2][K];
  10. ll query(int l, int r) {
  11. int len = r - l + 1;
  12. int j = log2(len);
  13. return min(st[l][j], st[r - (1 << j) + 1][j]);
  14. }
  15. ll solve(vector<int> &v) {
  16. int len = v.size();
  17. vector<ll> s = {0};
  18. for (int i = 0; i < 2 * len; ++i)
  19. s.emplace_back(s.back() + v[i % len]);
  20. int slen = s.size();
  21. for (int i = 0; i < slen; ++i)
  22. st[i][0] = s[i];
  23. for (int j = 1; j <= log2(slen); ++j)
  24. for (int i = 0; i < slen; ++i) {
  25. st[i][j] = st[i][j - 1];
  26. int right = i + (1 << (j - 1));
  27. if (right < slen)
  28. st[i][j] = min(st[i][j], st[right][j - 1]);
  29. }
  30. ll sum = s[len], hi_r = LO, hi_all = LO;
  31. int r = k % len;
  32. for (int i = 1; i < slen; ++i) {
  33. if (r)
  34. hi_r = max(hi_r, s[i] - query(max(0, i - r), i - 1));
  35. hi_all = max(hi_all, s[i] - query(max(0, i - len), i - 1));
  36. }
  37. if (k < len)
  38. return hi_r;
  39. return max(hi_all, max(sum * (k / len - 1) + hi_all, sum * (k / len) + hi_r));
  40. }
  41. int main() {
  42. cin >> n >> k;
  43. vector<int> p(n), c(n);
  44. for (int i = 0; i < n; ++i)
  45. cin >> p[i];
  46. for (int i = 0; i < n; ++i)
  47. cin >> c[i];
  48. ll ans = LO;
  49. vector<bool> vis(n);
  50. for (int i = 0; i < n; ++i) {
  51. if (vis[i])
  52. continue;
  53. vector<int> v;
  54. int idx = i;
  55. while (!vis[p[idx] - 1]) {
  56. idx = p[idx] - 1;
  57. vis[idx] = true;
  58. v.emplace_back(c[idx]);
  59. }
  60. ans = max(ans, solve(v));
  61. }
  62. cout << ans;
  63. }

E - Picking Goods

AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图40AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图41 列的方阵,其中有 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图42 个格子里有东西,第ii个东西的价值为 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图43。从左上角走到右下角,只能向下或向右走,限定每行最多拿 $ 3$ 个东西,求能取得的最大价值。

简单的方阵 DP 再加一维记录当前行取了几个东西即可。因为AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图44 是常数,所以总时间复杂度为:AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图45#card=math&code=%5Cmathcal%7BO%7D%28RC%29)。

  1. // Murabito-B 21/04/08
  2. #include <bits/stdc++.h>
  3. using ll = long long;
  4. using namespace std;
  5. ll dp[3010][3010][4] = {0};
  6. int main() {
  7. ios_base::sync_with_stdio(false), cin.tie(0);
  8. int R, C, K;
  9. cin >> R >> C >> K;
  10. vector<vector<int>> a(R + 1, vector<int>(C + 1));
  11. for (int i = 0; i < K; ++i) {
  12. int r, c, v;
  13. cin >> r >> c >> v;
  14. a[r][c] = v;
  15. }
  16. for (int i = 1; i <= R; ++i)
  17. for (int j = 1; j <= C; ++j) {
  18. for (int k = 0; k <= 3; ++k)
  19. dp[i][j][0] = max(dp[i][j][0], dp[i - 1][j][k]);
  20. for (int k = 0; k <= 3; ++k)
  21. dp[i][j][k] = max(dp[i][j][k], dp[i][j - 1][k]);
  22. if (a[i][j])
  23. for (int k = 3; k > 0; --k)
  24. dp[i][j][k] = max(dp[i][j][k], dp[i][j][k - 1] + a[i][j]);
  25. }
  26. ll ans = 0;
  27. for (int i = 0; i <= 3; ++i) ans = max(ans, dp[R][C][i]);
  28. cout << ans << "\n";
  29. return 0;
  30. }

F - Making Palindrome

F 题是懵逼ing

AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图46AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图47)个长度不超过 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图48AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图49)的字符串,每个字符串可以使用无限次,第ii个字符串使用一次的代价为 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图50。问最少花费多少代价,能够用这些字符串组成一个回文串?或者说明无解。

大佬题解:

直接搜索,状态似乎是无穷无尽的。如何减少状态空间,让搜索变为可能?

我们考虑从左右两边分别构建字符串。最开始,左边和右边都是空的。我们希望最后能将左边部分和右边部分进行匹配。这里,匹配的意思是,对于串 AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图51AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图52,两串中较短的那串是较长那串的子串。在匹配之后,如果剩下的部分是一个回文串(或为空),则我们就成功构建了一个回文串。

我们每次可以把某个字符串加入到左边或右边,这样就得到一个中间状态。在转移过程中,我们应当保证始终只有至多一边有未匹配部分,而其余部分都应该得到匹配。也就是说,如果当前左边有未被匹配的部分,我们就把新字符串添加到右边;反之亦然。

从而,我们只需要保存当前未被匹配的部分。而因为我们总是在相反的一边添加,这里的未被匹配部分必定为原来某个字符串的前缀或后缀。这样,我们就把总状态数限制到了AtCoder Beginner Contest 175 (AB水,C数学,D思维 前缀和处理 进价思考,E方阵 条件DP,F新回文字符串处理 GJ) - 副本 - 图53#card=math&code=O%28NL%29)。

此时,原题就变成了一个最短路径问题。因为数据范围很小,可以用各种最短路径算法来求解。

  1. // Murabito-B 21/04/08
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. using ll = long long;
  5. #define INF 10000000000000000LL
  6. bool is_palindrome(string &s) {
  7. int n = s.size();
  8. for (int i = 0; i < n / 2; ++i)
  9. if (s[i] != s[n - i - 1])
  10. return false;
  11. return true;
  12. }
  13. int n;
  14. unordered_map<string, ll> memo[2];
  15. unordered_set<string> vis[2];
  16. vector<string> S[2];
  17. vector<ll> C;
  18. ll dfs(string s, int p) {
  19. if (memo[p].count(s))
  20. return memo[p][s];
  21. if (is_palindrome(s))
  22. return 0;
  23. if (vis[p].count(s))
  24. return INF;
  25. vis[p].insert(s);
  26. ll ans = INF;
  27. int ls = s.size();
  28. for (int i = 0; i < n; ++i) {
  29. string t = S[!p][i];
  30. int lt = t.size();
  31. int l = min(ls, lt);
  32. string ps = s.substr(0, l);
  33. string pt = t.substr(0, l);
  34. if (ps != pt)
  35. continue;
  36. ll cost =
  37. ls > lt ? dfs(s.substr(l, ls - l), p) : dfs(t.substr(l, lt - l), !p);
  38. if (cost < ans)
  39. ans = min(ans, cost + C[i]);
  40. }
  41. vis[p].erase(s);
  42. memo[p][s] = ans;
  43. return ans;
  44. }
  45. int main() {
  46. cin >> n;
  47. S[0] = vector<string>(n);
  48. S[1] = vector<string>(n);
  49. C = vector<ll>(n);
  50. ll ans = INF;
  51. for (int i = 0; i < n; ++i) {
  52. cin >> S[0][i] >> C[i];
  53. S[1][i] = string(S[0][i].rbegin(), S[0][i].rend());
  54. }
  55. for (int i = 0; i < n; ++i)
  56. ans = min(ans, dfs(S[0][i], 0) + C[i]);
  57. cout << (ans == INF ? -1 : ans);
  58. }