正文
20250802
订正钉耙编程5
1. 1001 一个更无聊的游戏
题意
给定一棵 n n n 个节点的树,每个节点有一对参数 ( a i , b i ) . (a_i, b_i). (ai,bi). 在树上进行 q q q次游戏,每次给定一组 ( x , y ) , (x, y), (x,y), 允许你在节点 x x x放置一枚棋子在树上任意移动。游戏过程中,每当第一次经过某个节点时(包括初始时的节点 x x x),若 y ≥ a i , y \geq a_i, y≥ai, 则令 y ← y + b i ; y \leftarrow y + b_i; y←y+bi; 否则该轮游戏结束,得分为当前的 y y y值。求每次游戏的最大得分。
1 ≤ n , q ≤ 1 0 5 , ∑ n , ∑ q ≤ 5 × 1 0 5 ; 0 ≤ a i , b i , y ≤ 1 0 12 . 1 \leq n, q \leq 10 ^ 5, \sum n, \sum q \leq 5 \times 10 ^ 5; \space 0 \leq a_i, b_i, y \leq 10 ^ {12}. 1≤n,q≤105,∑n,∑q≤5×105; 0≤ai,bi,y≤1012.
做法
考虑暴力做法,每个关卡从起点开始遍历,把相邻的点加入一个堆,每次取 a i a_i ai 最小的点并扩展连通块。
考虑按照 a i a_i ai 从小到大依次加点,用 DSU 维护连通块以及其内可以抵达整个连通块的关卡(一定是在过程中逐渐减少的),加入一个点时再考虑会受到这个点限制的关卡,就是起点在与它相邻的联通块内的某些关卡,因为联通块内的点的所有 a j a_j aj 的限制都不及当前点,所以可以走遍连通块,吃遍联通块内所有 b j b_j bj,再加上它的初值与当前 a i a_i ai 比较。块内的 a j a_j aj 维护可以用堆或者平衡树,容易启发式合并,复杂度 O ( n log 2 n ) O(n\log^2n) O(nlog2n)。
代码
cpp
#include <bits/stdc++.h>
#define MP std::make_pair
using pii = std::pair<long long, int>;
void solve() {
int n, Q;
std::cin >> n >> Q;
struct node {
long long req, val;
int i;
};
std::vector<node> a(n + 1);
for (int i = 1; i <= n; i++) {
std::cin >> a[i].req;
a[i].i = i;
}
for (int i = 1; i <= n; i++) {
std::cin >> a[i].val;
}
std::sort(a.begin() + 1, a.end(), [](const node x, const node y) {
return x.req == y.req ? x.i < y.i : x.req < y.req;
});
std::vector<int> pos(n + 1);
for (int i = 1; i <= n; i++) {
pos[a[i].i] = i;
}
std::vector<std::vector<int> > g(n + 1);
for (int i = 1; i < n; i++) {
int u, v;
std::cin >> u >> v;
g[std::max(pos[u], pos[v])].push_back(std::min(pos[u], pos[v]));
}
std::vector<std::priority_queue<pii, std::vector<pii>, std::greater<pii> > > q(n + 1);
std::vector<long long> ans(Q + 1, -1), init_val(Q + 1);
for (int i = 1; i <= Q; i++) {
long long x, y;
std::cin >> x >> y;
init_val[i] = y;
if (y < a[pos[x]].req) {
ans[i] = y;
} else {
q[pos[x]].push(MP(y, i));
}
}
std::vector<long long> fa(n + 1), sum(n + 1);
auto find = [&](auto self, int x) -> int {
return fa[x] == x ? x : (fa[x] = self(self, fa[x]));
};
auto merge = [&](int x, int y) -> void {
int rx = find(find, x), ry = find(find, y);
if (rx == ry) {
return;
}
fa[rx] = ry;
sum[ry] += sum[rx];
sum[rx] = 0;
if (q[rx].size() > q[ry].size()) {
std::swap(q[rx], q[ry]);
}
while (!q[rx].empty()) {
q[ry].push(q[rx].top());
q[rx].pop();
}
};
for (int i = 1; i <= n; i++) {
fa[i] = i;
sum[i] = a[i].val;
}
for (int u = 1; u <= n; u++) {
for (int v : g[u]) {
int root = find(find, v);
while (!q[root].empty()) {
long long now = q[root].top().first + sum[root];
if (now < a[u].req) {
ans[q[root].top().second] = now;
q[root].pop();
} else {
break;
}
}
}
for (int v : g[u]) {
merge(v, u);
}
}
long long all_val = sum[find(find, 1)];
for (int i = 1; i <= Q; i++) {
if (ans[i] == -1) {
std::cout << init_val[i] + all_val << '\n';
} else {
std::cout << ans[i] << '\n';
}
}
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
int t;
std::cin >> t;
while (t--) {
solve();
}
}
20250803
2. 2126F - 1-1-1, Free Tree!
题意
给一棵树,每个点有颜色,每条边有权值,一棵树的总价值定义为每条边的价值和,每条边的价值为:如果这条边两端点颜色相同为 0 0 0,否则为这条边的权值。若干次操作:永久修改一个点的颜色,每次操作后输出这棵树的总价值。
做法
考虑暴力做法:维护 v a l ( u , c l r ) val(u,clr) val(u,clr) 表示从节点 u u u 出发的另一端颜色为 c l r clr clr 的边的权值和,由于要修改连接一个节点的所有边的另一端,所以容易被菊花图卡掉。以上做法的瓶颈在于一条边的权值在两个地方记录,维护需要考虑两端,并且单次操作要修改的内容过多,所以下面考虑优化这一点。
我们希望使得一条边的权值只在一个端点处被维护并且每次修改一个点时只需修改一个地方(因为总维护数为边数,而树上边数与点数几乎相等,所以希望一个点对应一条边),这样想到定根,每个点维护它的儿子边的信息,每次修改只需要往上修改父亲的信息即可。