This problem is sourced from the 2021 ICPC Asia Shanghai Regional Programming Contest.
Problem Link: https://codeforces.com/gym/103446/problem/H
In this problem, you are given an undirected connected graph with \(n\) cities and \(m\) undirected roads. The \(i\)-th city provides \(a_i\) social skill points the first time you reach it. Furthermore, every road requires a certain amount of social skills to traverse. The goal is to answer \(q\) queries, where a query is: What is the maximum social skill you can obtain if you start at node \(x\) with \(k\) social skill?
The bounds are:
Suppose we have a person who starts off at city \(x\) with social skill \(k\). Initially, suppose the graph starts off all \(n\) nodes but no edges, and we progressively add edges of increasing skill level. Each time we add an edge weight \(w\) that connects component \(X \ni x\) with component \(Y\), then there are two cases:
This process is \(O(n+m)\) for each person, so doing this per person would give a time of \(O(q(n+m))\). We can improve on this by taking advantage of small merge to handle all people at once. We start off with the no edge graph, where each one-node component also contains a set of the people which start at that node. We then run Kruskal’s algorithm on increasing edge weights to merge components together, maintaining each components sum of skills in the DSU. Before each merge, we check the sets of both components and anyone who is unable to traverse the edge is removed and their final skill level is calculated, before merging the two sets together. If we sort the set by increasing skill level (e.g. using C++ sets), then we only need to remove off the front of the set, which takes \(O(\log q)\) time per person. Also, since each person is added and removed from the set at most once, this gives an amortised complexity of \(O(q \log q)\). Finally, we take care to always merge the smaller set into the larger one, so that we get an amortised complexity of \(O(q \log (q)^2)\) as per small merge.
The total complexity is \(O(m\log m + q\log (q)^2)\).
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define x first
#define y second
#define pb push_back
#define all(x) x.begin(), x.end()
#define sz(x) int(x.size())
#define rep(i, n) for (int i = 0; i < (n); i++)
#define MAXN 100005
struct Event {
int a, b, w;
bool person;
bool operator<(const Event &oth) const {
if (w == oth.w) return person < oth.person;
return w < oth.w;
}
};
int N, M, Q;
int a[MAXN];
<Event> es;
vector
int ans[MAXN];
int rep[MAXN];
int sz[MAXN];
int skill[MAXN]; // sum of skills in component
<pii> ss[MAXN]; // set of people in that component <initial skill, id>
set
void init() {
(i, N) {
rep[i] = i;
rep[i] = 1;
sz[i] = a[i];
skill}
}
int getrep(int x) {
return rep[x] == x ? x : (rep[x] = getrep(rep[x]));
}
void join(int x, int y) {
= getrep(x), y = getrep(y);
x if (sz[x] < sz[y]) swap(x, y);
[y] = x;
rep[x] += sz[y];
sz[x] += skill[y];
skill[x].insert(ss[y].begin(), ss[y].end());
ss[y].clear();
ss}
int main () {
::sync_with_stdio(false); cin.tie(NULL);
ios_base
>> N >> M >> Q;
cin (i, N) cin >> a[i];
rep(i, M) {
repint u, v, w;
>> u >> v >> w;
cin --, v--;
u.pb({u, v, w, false});
es}
(i, Q) {
repint x, k;
>> x >> k;
cin --;
x.pb({i, x, k, true});
es}
(all(es));
sort
();
init(ans, ans+N, -1);
fill
for (Event e : es) {
if (e.person) {
[getrep(e.b)].insert({e.w, e.a}); // put him into the right set
ss// Note: In this solution, we insert people into sets only after we
// we have considered all edges with weight <= than it. This does not
// effect the correctness of the solution.
} else {
// get leaders for two components
int x = getrep(e.a), y = getrep(e.b);
if (x == y) continue; // already merged
// remove the weak people who are gated by this edge, and
// calculate their final answer
while (!ss[x].empty() && ss[x].begin()->x + skill[x] < e.w) {
[ss[x].begin()->y] = ss[x].begin()->x + skill[x];
ans[x].erase(ss[x].begin());
ss}
while (!ss[y].empty() && ss[y].begin()->x + skill[y] < e.w) {
[ss[y].begin()->y] = ss[y].begin()->x + skill[y];
ans[y].erase(ss[y].begin());
ss}
// join them together
(x, y);
join}
}
// all nodes are merged, and anyone left is able to explore all of it
for (pii p : ss[getrep(0)]) {
[p.y] = p.x + skill[getrep(0)];
ans}
(i, Q) {
rep("%d\n", ans[i]);
printf}
}
This idea follows from the same observations made in the small merge solution. Form a DSU Tree on the graph, and let \(w(v)\) denote the weight of the edge represented by a non-leaf node \(v\) in the DSU Tree. For any query, we find the last node that doesn’t prevents us going any further. This indicates that the person’s social skill level can no longer improve, and their maximum social skill will be the sum of their initial skill and the sum of the social skills gained from all the leaf nodes in the subtree.
We can preprocess the DSU Tree to store the sum of skill in each subtree of node \(i\) as \(skill_i\) in \(O(n)\). To find the highest node that we can reach for a query with initial skill \(k\), observe that to traverse up from node \(u\) to its parent node \(v\), we require that \(k \ge w(v) - skill_u\). Therefore, we can use a jumplist, where each jump also stores the minimum initial skill required to make the jump. This allows us to query for each person the highest jump they can make in \(O(\log n)\) time.
The final complexity is \(O(m \log m + (q+n) \log n)\)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define x first
#define y second
#define pb push_back
#define all(x) x.begin(), x.end()
#define sz(x) int(x.size())
#define rep(i, n) for (int i = 0; i < (n); i++)
#define MAXN 200005
struct Edge {
int a, b, w;
bool operator<(const Edge &oth) const {
return w < oth.w;
}
};
int N, M, Q;
int a[MAXN]; // skill you can get from this node's subtree
int d[MAXN]; // edge weight, only non-zero for edge nodes.
// dsu
int rep[MAXN];
void init() { rep(i, MAXN) rep[i] = i; }
int getrep(int x) { return rep[x] == x ? x : (rep[x] = getrep(rep[x])); }
// build kruskal's refactoring tree, with weights
int par[MAXN];
<int> G[MAXN];
vectorvoid kruskal(vector<Edge> &es) {
(all(es));
sort();
init(par, par+MAXN, -1);
fillfor (Edge e : es) {
int x = getrep(e.a), y = getrep(e.b);
// printf("a:%d b:%d, x:%d y:%d\n", e.a, e.b, x, y);
if (x == y) continue;
// set skill level
[N] = a[x] + a[y];
a[N] = e.w;
d// connect x and y to N
[N].pb(x); G[N].pb(y);
G[x] = par[y] = N;
par[x] = rep[y] = N; // set N to be the new leader
rep// printf("kruskal: x:%d y:%d N:%d\n", x, y, N);
++;
N}
}
// build jumplist
int jump[MAXN][18];
int need[MAXN][18]; // how much skill you need to make the jump
void build(int at) {
// printf("build: at:%d\n", at);
[at][0] = par[at];
jumpif (par[at] != -1) need[at][0] = d[par[at]] - a[at];
for (int i = 1; i <= 17; i++) {
int to = jump[at][i-1];
if (to == -1) jump[at][i] = -1;
else {
[at][i] = jump[to][i-1];
jump[at][i] = max(need[at][i-1], need[to][i-1]);
need}
}
for (int to : G[at]) build(to);
}
int main () {
::sync_with_stdio(false); cin.tie(NULL);
ios_base
>> N >> M >> Q;
cin (i, N) cin >> a[i];
rep<Edge> es;
vector(i, M) {
repint u, v, w;
>> u >> v >> w;
cin --, v--;
u.pb({u, v, w});
es}
// build dsu tree
(es);
kruskal(N-1); // N-1 is the root
build
// handle queries
(_, Q) {
repint x, k;
>> x >> k;
cin --;
x// jump up as high as we can
for (int i = 17; i >= 0; i--) {
if (jump[x][i] == -1) continue;
if (need[x][i] > k) continue;
= jump[x][i];
x }
// skill is initial + sum of everything in subtree
("%d\n", a[x] + k);
printf}
}