补题链接:Here
A - Rainy Season
如果不是
RSR型的话直接计算R的数量即可
B - Making Triangle
给定 根长度分别为
的棍子,问能组成多少个三边长度各不相同的三角形?如果两个三角形至少用了一根不同编号的棍子,则称它们是不同的三角形。
由于数据范文较小 (),所以我们可以排序以后枚举三元组即可。
另外 CP wiki 提到这里进一步优化的话,可以在固定最长边的基础上,用双指针确定另外两条边的长度范围,这样时间复杂度就降到的了 #card=math&code=%5Cmathcal%7BO%7D%28N%5E2%29)。
C - Walking Takahashi
题意:有一个数 ,对它进行
次
或
的操作,求操作后的
。
思路:
首先XX的正负不影响结果,所以我们可以只考虑 。
如果 ,那么我们首先应该向原点移动,直到
。这时还剩下
次操作,我们应当在原点的左右两侧来回移动。根据
的奇偶判断一下最后在哪一个位置即可。
using ll = long long;int main() {ios_base::sync_with_stdio(false), cin.tie(0);ll X, K, D, R;cin >> X >> K >> D;if (X < 0) X = -X;R = X / D;if (K < R) {cout << (X - K * D);return 0;}K -= R, X -= R * D;cout << (K & 1 ? D - X : X);return 0;}
D - Moving Piece
有 个方格,从第
个方格会跳到第
个方格。PP是
的一个排列。
每个方格上写了一个数字 。每次跳跃时,会得到等同于
的分数。你可以从任意方格开始,跳跃至少一次,至多
次,求能够取得的最高分数。
思路:枚举起点。由于 是排列,所以我们从任意位置
开始,经过若干次跳跃后一定会回到
。我们可以计算出一个周期内的前缀和。然后,根据周期长度
与
之间的关系,分情况讨论。
,此时我们应该选择前
个前缀和中的最大值。
,令
,则我们可以选择
- 不循环,选择所有前缀和中的最大值。
- 循环
次,再加上前
个前缀和中的最大值。
- 循环
次,再加上所有前缀和中的最大值。
#card=math&code=%5Cmathcal%7BO%7D%28N%5E2%29)
// Murabito-B 21/04/08#include <bits/stdc++.h>using ll = long long;using namespace std;int main() {ios_base::sync_with_stdio(false), cin.tie(0);int n, k;cin >> n >> k;vector<int> p(n), c(n);for (int i = 0; i < n; ++i) cin >> p[i];for (int i = 0; i < n; ++i) cin >> c[i];ll ans = LLONG_MIN; // long long的最小值for (int i = 0; i < n; ++i) {vector<bool> book(n);int idx = i;vector<ll> sum = {0}, hi = {LLONG_MIN};while (!book[p[idx] - 1]) {idx = p[idx] - 1;book[idx] = true;sum.emplace_back(sum.back() + c[idx]);hi.emplace_back(max(hi.back(), sum.back()));}int m = sum.size() - 1;int f = k / m, res = k % m;ll result = 0;if (f > 0) result = max(hi[m], max(sum[m] * f + (res == 0 ? 0 : hi[res]), sum[m] * (f - 1) + hi[m]));elseresult = hi[res];ans = max(ans, result);}cout << ans << "\n";return 0;}
另外如果 呢?应该如何改进算法?
这里想了很久,只想到了 RMQ解决但代码部分没写出来,只能转载一下 CP wiki 的了
提示一:
在上面的算法中,对于一个循环,设其长度为
,我们实际上重复计算了
次(针对每一个起点)。有没有可能减少这样的重复计算呢?
提示二
在每一个循环内,问题实际上可以转化为,给定一个由
个数围成的圈,从中取出长度不超过
的一段连续串,求能取得的最大和。
提示三
前缀和+RMQ。
// Murabito-B 21/04/08#include <bits/stdc++.h>using ll = long long;#define MAXN 5005#define K 15using namespace std;const ll LO = -1e16;int n, k;ll st[MAXN * 2][K];ll query(int l, int r) {int len = r - l + 1;int j = log2(len);return min(st[l][j], st[r - (1 << j) + 1][j]);}ll solve(vector<int> &v) {int len = v.size();vector<ll> s = {0};for (int i = 0; i < 2 * len; ++i)s.emplace_back(s.back() + v[i % len]);int slen = s.size();for (int i = 0; i < slen; ++i)st[i][0] = s[i];for (int j = 1; j <= log2(slen); ++j)for (int i = 0; i < slen; ++i) {st[i][j] = st[i][j - 1];int right = i + (1 << (j - 1));if (right < slen)st[i][j] = min(st[i][j], st[right][j - 1]);}ll sum = s[len], hi_r = LO, hi_all = LO;int r = k % len;for (int i = 1; i < slen; ++i) {if (r)hi_r = max(hi_r, s[i] - query(max(0, i - r), i - 1));hi_all = max(hi_all, s[i] - query(max(0, i - len), i - 1));}if (k < len)return hi_r;return max(hi_all, max(sum * (k / len - 1) + hi_all, sum * (k / len) + hi_r));}int main() {cin >> n >> k;vector<int> p(n), c(n);for (int i = 0; i < n; ++i)cin >> p[i];for (int i = 0; i < n; ++i)cin >> c[i];ll ans = LO;vector<bool> vis(n);for (int i = 0; i < n; ++i) {if (vis[i])continue;vector<int> v;int idx = i;while (!vis[p[idx] - 1]) {idx = p[idx] - 1;vis[idx] = true;v.emplace_back(c[idx]);}ans = max(ans, solve(v));}cout << ans;}
E - Picking Goods
行
列的方阵,其中有
个格子里有东西,第ii个东西的价值为
。从左上角走到右下角,只能向下或向右走,限定每行最多拿 $ 3$ 个东西,求能取得的最大价值。
简单的方阵 DP 再加一维记录当前行取了几个东西即可。因为 是常数,所以总时间复杂度为:
#card=math&code=%5Cmathcal%7BO%7D%28RC%29)。
// Murabito-B 21/04/08#include <bits/stdc++.h>using ll = long long;using namespace std;ll dp[3010][3010][4] = {0};int main() {ios_base::sync_with_stdio(false), cin.tie(0);int R, C, K;cin >> R >> C >> K;vector<vector<int>> a(R + 1, vector<int>(C + 1));for (int i = 0; i < K; ++i) {int r, c, v;cin >> r >> c >> v;a[r][c] = v;}for (int i = 1; i <= R; ++i)for (int j = 1; j <= C; ++j) {for (int k = 0; k <= 3; ++k)dp[i][j][0] = max(dp[i][j][0], dp[i - 1][j][k]);for (int k = 0; k <= 3; ++k)dp[i][j][k] = max(dp[i][j][k], dp[i][j - 1][k]);if (a[i][j])for (int k = 3; k > 0; --k)dp[i][j][k] = max(dp[i][j][k], dp[i][j][k - 1] + a[i][j]);}ll ans = 0;for (int i = 0; i <= 3; ++i) ans = max(ans, dp[R][C][i]);cout << ans << "\n";return 0;}
F - Making Palindrome
F 题是懵逼ing
有(
)个长度不超过
(
)的字符串,每个字符串可以使用无限次,第ii个字符串使用一次的代价为
。问最少花费多少代价,能够用这些字符串组成一个回文串?或者说明无解。
大佬题解:
直接搜索,状态似乎是无穷无尽的。如何减少状态空间,让搜索变为可能?
我们考虑从左右两边分别构建字符串。最开始,左边和右边都是空的。我们希望最后能将左边部分和右边部分进行匹配。这里,匹配的意思是,对于串
和
,两串中较短的那串是较长那串的子串。在匹配之后,如果剩下的部分是一个回文串(或为空),则我们就成功构建了一个回文串。
我们每次可以把某个字符串加入到左边或右边,这样就得到一个中间状态。在转移过程中,我们应当保证始终只有至多一边有未匹配部分,而其余部分都应该得到匹配。也就是说,如果当前左边有未被匹配的部分,我们就把新字符串添加到右边;反之亦然。
从而,我们只需要保存当前未被匹配的部分。而因为我们总是在相反的一边添加,这里的未被匹配部分必定为原来某个字符串的前缀或后缀。这样,我们就把总状态数限制到了
#card=math&code=O%28NL%29)。
此时,原题就变成了一个最短路径问题。因为数据范围很小,可以用各种最短路径算法来求解。
// Murabito-B 21/04/08#include <bits/stdc++.h>using namespace std;using ll = long long;#define INF 10000000000000000LLbool is_palindrome(string &s) {int n = s.size();for (int i = 0; i < n / 2; ++i)if (s[i] != s[n - i - 1])return false;return true;}int n;unordered_map<string, ll> memo[2];unordered_set<string> vis[2];vector<string> S[2];vector<ll> C;ll dfs(string s, int p) {if (memo[p].count(s))return memo[p][s];if (is_palindrome(s))return 0;if (vis[p].count(s))return INF;vis[p].insert(s);ll ans = INF;int ls = s.size();for (int i = 0; i < n; ++i) {string t = S[!p][i];int lt = t.size();int l = min(ls, lt);string ps = s.substr(0, l);string pt = t.substr(0, l);if (ps != pt)continue;ll cost =ls > lt ? dfs(s.substr(l, ls - l), p) : dfs(t.substr(l, lt - l), !p);if (cost < ans)ans = min(ans, cost + C[i]);}vis[p].erase(s);memo[p][s] = ans;return ans;}int main() {cin >> n;S[0] = vector<string>(n);S[1] = vector<string>(n);C = vector<ll>(n);ll ans = INF;for (int i = 0; i < n; ++i) {cin >> S[0][i] >> C[i];S[1][i] = string(S[0][i].rbegin(), S[0][i].rend());}for (int i = 0; i < n; ++i)ans = min(ans, dfs(S[0][i], 0) + C[i]);cout << (ans == INF ? -1 : ans);}
