Автор задачі: | Деркач Тарас |
Підготував: | Деркач Тарас |
Розбір: | Деркач Тарас |
Розв'язання
Зафіксуємо вершину , яку ми обираємо центральною.
Після цього видаляються всі вершини на відстані рівно від .
Подивимось, які зв'язані частини залишаться.
Перша частина завжди містить саму вершину та всіх її сусідів. Позначимо її суму як center_v:
Інші частини можуть з'явитися тільки за вершинами, які були видалені.
Нехай — сусід вершини , а — сусід вершини , причому x \ne v.
Тоді вершина знаходиться на відстані рівно від , тому вона видаляється. Після видалення можуть залишитися частини, які лежать за її іншими сусідами.
Тобто нам потрібно швидко знати таке значення:
якщо є ребро , то яка сума компоненти, що лежить з боку вершини , якщо прибрати це ребро.
Позначимо цю суму як side(x,y).
Якщо укорінити дерево в вершині , то side(x,y) легко рахується через суми піддерев:
якщо є сином , то side(x,y) = subtree_y;
інакше side(x,y) = total - subtree_x.
Для кожної вершини збережемо два найменші та два найбільші значення серед усіх side(x,y) по сусідах .
Навіщо два? Бо іноді нам треба взяти найкраще значення, але заборонити один конкретний напрямок. Наприклад, коли ми дивимось на видалену вершину , не можна брати напрямок назад до , бо звідти прийшли до .
Далі для кожної вершини збережемо найменші та найбільші суми компонент, які можуть з'явитися після видалення одного із сусідів вершини .
Іншими словами, для кожного сусіда вершини беремо найкращу компоненту за , але не в напрямку назад до .
Після такої підготовки відповідь для конкретної центральної вершини можна знайти швидко.
Спочатку маємо компоненту з сумою center_v.
Потім для кожного сусіда вершини беремо найменшу та найбільшу можливу компоненту за вершинами, які знаходяться за . При цьому треба заборонити напрямок назад до .
Серед усіх знайдених компонент знаходимо мінімальну та максимальну суму. Контраст для вершини дорівнює:
Відповідь — максимум цього значення серед усіх вершин .
Приклад
У першому прикладі оптимально вибрати вершину v = 4.
Тоді вершина знаходиться на відстані рівно від неї, тому вона видаляється.
Після видалення залишаються дві зв'язані частини:
із сумою ;
\{3,4,5\} із сумою 3 + 4 + 5 = 12.
Отже, контраст дорівнює:
Асимптотика
Ми один раз рахуємо суми піддерев, після цього для кожної вершини переглядаємо її сусідів.
Кожне ребро обробляється лише константну кількість разів.
Тому складність розв'язання становить .
Розв'язок
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll INF = (1LL << 62);
struct BestMin {
pair<ll, int> first = {INF, -1};
pair<ll, int> second = {INF, -1};
void add(ll value, int id) {
if (value < first.first) {
second = first;
first = {value, id};
} else if (value < second.first) {
second = {value, id};
}
}
ll get(int banned_id) {
if (first.second == banned_id) return second.first;
return first.first;
}
};
struct BestMax {
pair<ll, int> first = {-INF, -1};
pair<ll, int> second = {-INF, -1};
void add(ll value, int id) {
if (value > first.first) {
second = first;
first = {value, id};
} else if (value > second.first) {
second = {value, id};
}
}
ll get(int banned_id) {
if (first.second == banned_id) return second.first;
return first.first;
}
};
void solve() {
int n;
cin >> n;
vector<ll> a(n + 1);
ll total = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
total += a[i];
}
vector<vector<int>> g(n + 1);
for (int i = 1; i <= n - 1; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
vector<int> parent(n + 1, 0);
vector<int> order;
order.push_back(1);
for (int i = 0; i < (int)order.size(); i++) {
int v = order[i];
for (int to : g[v]) {
if (to == parent[v]) continue;
parent[to] = v;
order.push_back(to);
}
}
vector<ll> subtree(n + 1);
for (int i = 1; i <= n; i++) {
subtree[i] = a[i];
}
for (int i = n - 1; i >= 1; i--) {
int v = order[i];
subtree[parent[v]] += subtree[v];
}
vector<BestMin> edge_min(n + 1);
vector<BestMax> edge_max(n + 1);
for (int x = 1; x <= n; x++) {
for (int y : g[x]) {
ll side_sum;
if (parent[y] == x) {
side_sum = subtree[y];
} else {
side_sum = total - subtree[x];
}
edge_min[x].add(side_sum, y);
edge_max[x].add(side_sum, y);
}
}
vector<BestMin> behind_min(n + 1);
vector<BestMax> behind_max(n + 1);
for (int u = 1; u <= n; u++) {
for (int x : g[u]) {
ll mn = edge_min[x].get(u);
ll mx = edge_max[x].get(u);
if (mn != INF) {
behind_min[u].add(mn, x);
}
if (mx != -INF) {
behind_max[u].add(mx, x);
}
}
}
ll answer = 0;
for (int v = 1; v <= n; v++) {
ll center = a[v];
for (int u : g[v]) {
center += a[u];
}
ll mn = center;
ll mx = center;
for (int u : g[v]) {
ll cur_min = behind_min[u].get(v);
ll cur_max = behind_max[u].get(v);
if (cur_min != INF) {
mn = min(mn, cur_min);
}
if (cur_max != -INF) {
mx = max(mx, cur_max);
}
}
answer = max(answer, mx - mn);
}
cout << answer << endl;
}
int main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}