补题连接:Hereios
若是不是
RSR
型的话直接计算R
的数量便可c++
给定 \(N\) 根长度分别为 \(L_i\) 的棍子,问能组成多少个三边长度各不相同的三角形?若是两个三角形至少用了一根不一样编号的棍子,则称它们是不一样的三角形。算法
因为数据范文较小 (\(N \le 100\)),因此咱们能够排序之后枚举三元组便可。优化
另外 CP wiki 提到这里进一步优化的话,能够在固定最长边的基础上,用双指针肯定另外两条边的长度范围,这样时间复杂度就降到的了 \(\mathcal{O}(N^2)\)。spa
题意:有一个数 \(X\) ,对它进行 \(K\) 次 \(+D\) 或 \(−D\) 的操做,求操做后的 \(\min|X'|\)。指针
思路:code
首先XX的正负不影响结果,因此咱们能够只考虑 \(|X|\)。排序
若是 \(|X|>D\),那么咱们首先应该向原点移动,直到 \(|X'|<D\)。这时还剩下 \(K′\) 次操做,咱们应当在原点的左右两侧来回移动。根据 \(K′\) 的奇偶判断一下最后在哪个位置便可。ci
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; }
有 \(N(N\leq5000)\)个方格,从第 \(i\) 个方格会跳到第 \(P_i\) 个方格。PP是 \(1,\cdots,N\)的一个排列。字符串
每一个方格上写了一个数字 \(C_i\) 。每次跳跃时,会获得等同于 \(C_{P_i}\) 的分数。你能够从任意方格开始,跳跃至少一次,至多 \(K\) 次,求可以取得的最高分数。
思路:枚举起点。因为 \(P\) 是排列,因此咱们从任意位置 \(i\) 开始,通过若干次跳跃后必定会回到 \(i\) 。咱们能够计算出一个周期内的前缀和。而后,根据周期长度 \(C\) 与 \(K\) 之间的关系,分状况讨论。
\(K\leq C\),此时咱们应该选择前 \(K\) 个前缀和中的最大值。
\(K>C\),令\(K=nc+r\),则咱们能够选择
\(\mathcal{O}(N^2)\)
// 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])); else result = hi[res]; ans = max(ans, result); } cout << ans << "\n"; return 0; }
另外若是 \(N \le 10^5\) 呢?应该如何改进算法?
这里想了好久,只想到了 RMQ解决但代码部分没写出来,只能转载一下 CP wiki 的了
提示一:
在上面的算法中,对于一个循环,设其长度为 \(L\) ,咱们实际上重复计算了 \(L\) 次(针对每个起点)。有没有可能减小这样的重复计算呢?
提示二
在每个循环内,问题实际上能够转化为,给定一个由 \(L\) 个数围成的圈,从中取出长度不超过\(K\)的一段连续串,求能取得的最大和。
提示三
前缀和+RMQ。
// Murabito-B 21/04/08 #include <bits/stdc++.h> using ll = long long; #define MAXN 5005 #define K 15 using 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; }
\(R\) 行 \(C\) 列的方阵,其中有 \(K\) 个格子里有东西,第ii个东西的价值为 \(v_i\)。从左上角走到右下角,只能向下或向右走,限定每行最多拿 $ 3$ 个东西,求能取得的最大价值。
简单的方阵 DP 再加一维记录当前行取了几个东西便可。由于\(3\) 是常数,因此总时间复杂度为:\(\mathcal{O}(RC)\)。
// 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 题是懵逼ing
有\(N\)(\(N\leq50\))个长度不超过 \(L\)(\(L\leq20\))的字符串,每一个字符串能够使用无限次,第ii个字符串使用一次的代价为 \(C_i\)。问最少花费多少代价,可以用这些字符串组成一个回文串?或者说明无解。
大佬题解:
直接搜索,状态彷佛是无穷无尽的。如何减小状态空间,让搜索变为可能?
咱们考虑从左右两边分别构建字符串。最开始,左边和右边都是空的。咱们但愿最后能将左边部分和右边部分进行匹配。这里,匹配的意思是,对于串 \(A\) 和 \(B\),两串中较短的那串是较长那串的子串。在匹配以后,若是剩下的部分是一个回文串(或为空),则咱们就成功构建了一个回文串。
咱们每次能够把某个字符串加入到左边或右边,这样就获得一个中间状态。在转移过程当中,咱们应当保证始终只有至多一边有未匹配部分,而其他部分都应该获得匹配。也就是说,若是当前左边有未被匹配的部分,咱们就把新字符串添加到右边;反之亦然。
从而,咱们只须要保存当前未被匹配的部分。而由于咱们老是在相反的一边添加,这里的未被匹配部分一定为原来某个字符串的前缀或后缀。这样,咱们就把总状态数限制到了\(O(NL)\)。
此时,原题就变成了一个最短路径问题。由于数据范围很小,能够用各类最短路径算法来求解。
// Murabito-B 21/04/08 #include <bits/stdc++.h> using namespace std; using ll = long long; #define INF 10000000000000000LL bool 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); }