2024 暑假友谊赛 3
2024 暑假友谊赛 3
A - A
思路
设 \(f_i\) 表示以 \(i\) 为根的子树产生的贡献,则有 \(f_i=size_i+\sum\limits_{j\in son_i} f_j\),即起初选定 \(i\) 为起点后产生 \(size_i\) 的贡献,后续是它的子树产生的贡献。
但这样以不同根节点去求贡献是 \(O(n^2)\) 的,所以考虑换根 dp。
设 \(dp_i\) 表示为以 \(i\) 为根的答案。
以上图为例,设整棵树大小为 \(n\) ,我们要将 \(x\) 的答案换根到 \(y\) 上:
即换根 dp 最终转移方程,当 \(n=1\) 时,有 \(dp_1=f_1\)。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector g(n + 1, vector<int>()); for (int i = 1; i < n; i ++) { int u, v; cin >> u >> v; g[u].emplace_back(v); g[v].emplace_back(u); } vector<i64> f(n + 1), dp(n + 1), siz(n + 1); auto dfs = [&](auto && self, int u, int fa)->void{ siz[u] = 1; for (auto v : g[u]) { if (v == fa) continue; self(self, v, u); siz[u] += siz[v]; f[u] += f[v]; } f[u] += siz[u]; }; dfs(dfs, 1, 0); i64 ans = 0; ans = dp[1] = f[1]; auto dpdfs = [&](auto && self, int u, int fa)->void{ if (u != 1) { dp[u] = dp[fa] + n - 2 * siz[u]; ans = max(ans, dp[u]); } for (auto v : g[u]) { if (v == fa) continue; self(self, v, u); } }; dpdfs(dpdfs, 1, 0); cout << ans << '\n'; return 0; }
B - B
思路
很经典的一道题,把能被 \(3\) 整除或者能 \(× 2\) 得到的数看成由 \(x\) 到 \(y\) 的一条有向边,这样就转换成了 \(DAG\) 模型,要求一条长度为 \(n\) 的变化方案,其实就是这个模型上的最长路,上拓扑序即可。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector<i64> a(n + 1); for (int i = 1; i <= n; i ++) cin >> a[i]; vector<int> in(n + 1); vector g(n + 1, vector<int>()); for (int i = 1; i <= n; i ++) { for (int j = 1; j <= n; j ++) { if (j == i) continue; if (a[i] * 2 == a[j]) { g[i].push_back(j); in[j] ++; } if (a[i] % 3 == 0 && a[i] / 3 == a[j]) { g[i].push_back(j); in[j] ++; } } } queue<int> Q; for (int i = 1; i <= n; i ++) { if (!in[i]) { Q.push(i); } } vector<int> ans; while (Q.size()) { auto u = Q.front(); Q.pop(); ans.push_back(u); for (auto v : g[u]) { if (!--in[v]) { Q.push(v); } } } for (auto i : ans) cout << a[i] << " \n"[i == ans.back()]; return 0; }
C - C
思路
注意到选择的 \(x\) 和 \(y\) 会变成两个数 \(x\& y\) 和 \(x|y\),其实以二进制的角度来看,就是这两个数的对应位上的 \(1\) 发生了转移,但是总共的 \(1\) 的个数未变,题目要求 \(a_i^2\),则 \(a_i\) 应该越大对答案的贡献才会越大,所以存下每个数对应位上的 \(1\) 有多少个,贪心地去凑出最大的数即可。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector<i64> a(n + 1), cnt(30); for (int i = 1; i <= n; i ++) { cin >> a[i]; for (int j = 0; j < 20; j ++) { if (a[i] >> j & 1) cnt[j] ++; } } i64 ans = 0; for (int i = 1; i <= n; i ++) { int x = 0; for (int j = 0; j < 20; j ++) { if (cnt[j]) { x += 1 << j; cnt[j] --; } } ans += 1ll * x * x; } cout << ans << '\n'; return 0; }
D - D
思路
当 \(p_i=i\) 时,那它和旁边的数交换一定可以使得两边的数都不等于其下标,所以遍历一遍,碰到 \(p_i=i\) 的直接和旁边的数交换一下记录答案即可。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector<int> p(n + 1); for (int i = 1; i <= n; i ++) { cin >> p[i]; } int ans = 0; for (int i = 1; i <= n; i ++) { if (p[i] != i) continue; if (i + 1 <= n) swap(p[i], p[i + 1]); else swap(p[i], p[i - 1]); ans ++; } cout << ans << '\n'; return 0; }
E - E
思路
一道细节题。
贪心的思路是排序后 \(a\) 和 \(b\) 依次把最小字母和最大字母往前面填,但当 \(S_a>S_b\) 的时候,\(a,b\) 往前填反而会成全对方,所以这个时候得往后填。
写法不同对细节的处理有不同。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); string a, b; cin >> a >> b; sort(a.begin(), a.end()); sort(b.begin(), b.end(), greater<>()); int n = a.size(); string ans1 = "", ans2 = ""; while (a.size() > (n + 1) / 2) a.pop_back(); while (b.size() > (n) / 2) b.pop_back(); while (n--) { if (a[0] < b[0]) { ans1 += a[0]; a.erase(a.begin()); } else { ans2 += a.back(); a.pop_back(); } if (n) { if (a[0] < b[0]) { ans1 += b[0]; b.erase(b.begin()); } else { ans2 += b.back(); b.pop_back(); } n--; } } reverse(ans2.begin(), ans2.end()); cout << ans1 + ans2 << '\n'; return 0; }
F - F
思路
这几天有点魔怔了,看到子树 (u,v) 对什么的老是想到树上启发式,唉,杭电害得。
对 \(x\) 子树中距离小于等于 \(k\) 的点全都加上一个值 \(x\),假设这棵树只有一条链,那很显然,答案其实就是做一个差分,然后跑一个前缀和。
现在是多条链,但是这多条链上的点都需要加上 \(x\),那么不妨将深度看成一个序列,在深度上进行差分,用 dfs 跑前缀和,这样就完成了树上差分以及区间求值的操作。
需要注意的是,因为一棵树有不同的子树,也就是有许多不同的链,在对当前子树做完差分的操作,回溯的时候要记得还原,否则会影响其他子树的答案。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector g(n + 1, vector<int>()); for (int i = 1; i < n; i ++) { int u, v; cin >> u >> v; g[u].push_back(v); g[v].push_back(u); } int m; cin >> m; vector Q(n + 1, vector<pair<int, int>>()); while (m--) { int v, d, x; cin >> v >> d >> x; Q[v].emplace_back(d, x); } vector<i64> pre(n + 1), ans(n + 1); auto dfs = [&](auto && self, int u, int fa, int dep, i64 sum)->void{ for (auto &[d, x] : Q[u]) { pre[dep] += x; int to = dep + d + 1; if (to <= n) { pre[to] -= x; } } sum += pre[dep]; ans[u] = sum; for (auto v : g[u]) { if (v == fa) continue; self(self, v, u, dep + 1, sum); } for (auto &[d, x] : Q[u]) { pre[dep] -= x; int to = dep + d + 1; if (to <= n) { pre[to] += x; } } }; dfs(dfs, 1, 0, 0, 0); for (int i = 1; i <= n; i ++) cout << ans[i] << " \n"[i == n]; return 0; }
G - G
思路
考虑 dp。
设 \(dp_{i,j}\) 表示以从 \(j\) 到 \(i\) 构成的数字(以下称做\(num_{i,j}\))作为结尾的方案数。
设 \(L=i-j\) 表示为该数字的长度,那么显然,大于这个长度的一定不可能转移过来,而小于这个长度的字符串构成的数字也一定小于 \(num_{i,j}\),那么这一步得到的转移方程为:
这一步可以用前缀和优化。
接下来就是考虑相等的情况,相等的情况下可以通过 \(LCP(\text{最长公共前缀})\) 的 \(n^2\) 算法预处理,然后判断 \(lcp\) 后的第一位大小情况即可,也可以通过 \(Sam\) 算法等。
我这里采用的是 二分+Hash 的做法(也有倍增+Hash),会比 \(n^2\) 的做法多一个 \(log\),总复杂度 \(O(n^2logn)\)
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; struct Hash { using u64 = unsigned long long; u64 base = 13331; vector<u64> pow, hash; Hash(string &s) { int N = s.size(); pow.resize(N + 1), hash.resize(N + 1); pow[0] = 1, hash[0] = 0; for (int i = 1; i < s.size(); i ++) { pow[i] = pow[i - 1] * base; hash[i] = hash[i - 1] * base + s[i]; } } u64 get(int l, int r) { return hash[r] - hash[l - 1] * pow[r - l + 1]; } //拼接两个子串 u64 link(int l1, int r1, int l2, int r2) { return get(l1, r1) * pow[r2 - l2 + 1] + get(l2, r2); } bool same(int l1, int r1, int l2, int r2) { return get(l1, r1) == get(l2, r2); } }; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; string s; cin >> s; s = " " + s; Hash hash(s); auto check = [&](int x, int y)->bool{ int l = 0, r = y - x, ans = 0; while (l <= r) { int mid = l + r >> 1; if (hash.same(x, x + mid - 1, y, y + mid - 1)) l = mid + 1, ans = mid; else r = mid - 1; } if (ans == y - x) return false; return s[ans + x] < s[ans + y]; }; vector dp(n + 1, vector<i64>(n + 1)); vector sum(n + 1, vector<i64>(n + 1)); for (int i = 1; i <= n; i ++) dp[i][1] = 1; const i64 mod = 1e9 + 7; for (int i = 1; i <= n; i ++) { for (int j = 1; j <= i; j ++) { if (s[j] == '0') continue; int k = max(1, j - (i - j)); (dp[i][j] += (sum[j - 1][j - 1] - sum[j - 1][k - 1] + mod) % mod) %= mod; k --; if (k >= 1 && check(k, j)) { (dp[i][j] += dp[j - 1][k]) %= mod; } } for (int j = 1; j <= i; j ++) sum[i][j] = (sum[i][j - 1] + dp[i][j]) % mod; } i64 ans = 0; for (int i = 1; i <= n; i ++) ans = (ans + dp[n][i]) % mod; cout << ans << '\n'; return 0; }
H - H
思路
要获得均分首先得保证 \(Sum_a\bmod n=0\)。
考虑一种做法就是,首先将所有的数都汇集到 \(a_1\) 上,然后其他数就是 \(0\) 了,然后由 \(a_1\) 统一分配 \(Avg\) 平均数给其他 \(n-1\) 个数,这样的做法有 \(2\times (n-1)\) 次操作。
期间会有一些数会产生余数,那么不妨让 \(a_1\) ‘借’点数给它使得被 \(i\) 整除,然后又一并还给 \(a_1\),题目保证 \(1\le a_i\le 1e5\),所以最开始一定有 \(1\) 去弥补 \(i=2\) 的余数,然后 \(a_2\) 把所有数给 \(a_1\) 后,又能保证有一定的数弥补 \(i=3 \dots\) 这样的操作最多也就 \((n-1)\) 次,所以总次数不会超过 \(3(n-1)\),满足题目要求。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; void solve() { int n; cin >> n; i64 sum = 0; vector<int> a(n + 1); for (int i = 1; i <= n; i ++) { cin >> a[i]; sum += a[i]; } if (sum % n != 0) { cout << -1 << '\n'; return ; } int avg = sum / n; vector<array<int, 3>> ans; for (int i = 2; i <= n; i ++) { int x; if (a[i] % i != 0) { x = i - a[i] % i; a[1] -= x; a[i] += x; ans.push_back({1, i, x}); } x = a[i] / i; a[1] += a[i]; a[i] = 0; ans.push_back({i, 1, x}); } for (int i = 2; i <= n; i ++) { ans.push_back({1, i, avg - a[i]}); } cout << ans.size() << '\n'; for (auto [a, b, c] : ans) { cout << a << ' ' << b << ' ' << c << '\n'; } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { solve(); } return 0; }
本文作者:Ke_scholar
本文链接:https://www.cnblogs.com/Kescholar/p/18341364
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。