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;
}