【ARC115F】Migration 题解(trick,贪心)
【ARC115F】Migration
题意:
给你一棵 \(n\) 个点的树, 每个点有一个权值 \(h_i\)。
你有 \(m\) 个棋子,初始的位置为 \(\{s_i\}\),你想要把它们移动到 \(\{t_i\}\)。
每次你可以选择一个棋子,将它移动到这个棋子所在节点的一个相邻节点上。一个节点上可以有多个棋子。
定义一个局面 \(S\) 的权值 \(f(S) = \sum_{i = 1}^{m}h_{p_i}\),其中 \(p_i\) 表示 \(i\) 号棋子当前的位置。
求移动过程中 \(f(S)\) 最大值的最小值是多少。
数据规模:
\(1 \leq m \leq n \leq 2000\), \(1 \leq h_i \leq 10^9\)。
分析:
感觉是没见过的东西。
设初始状态为 \(S\),最终状态为 \(T\)。
首先要求最大值的最小值,考虑二分答案。设二分值为 \(x\)。
那么如果我们将所有能相互到达并且合法(\(f\) 小于等于 \(x\))的状态连边,那么需要判断 \(S\) 和 \(T\) 是否在同一个连通块里。
直接看 \(S\) 能否到 \(T\) 或者 \(T\) 能否到 \(S\) 是困难的。我们考虑找到一个中间状态 \(U\),如果 \(S\) 和 \(T\) 都和 \(U\) 在一个联通块里,那么二者就在一个连通块里。
设 \(g(S)\) 为 \(S\) 局面能够到达的 权值最小 的 局面(如果权值相同就按照字典序依次比较)。那么 \(S\) 和 \(T\) 在同一个连通块的 充要条件 是 \(g(S) = g(T)\)。
问题变成了在满足限制的条件下求 \(g(S)\):
注意到 \(g(S)\) 一定能通过不断将 \(S\) 中一个点 \(x\),移动到另一个点 \(x'\) 满足 \(h_{x'} < h_{x}\) 或者 \(h_{x'} = h_{x},x' < x\) 得到。
我们称上述操作为拓展一个点 \(x\)。
所有点最多被拓展 \(n\) 次,因此总共会拓展 \(nk\) 次。并且每个点被拓展的顺序是没有要求的,因为拓展之后和变小,更容易满足限制。
设 \(x'\) 为 \(x\) 的“拓展点”, 则需要满足, \(h_{x'} < h_{x}\) 或 \(h_{x'} = h_{x},x' < x\)。
由于我们要尽量满足限制,并且只要能拓展,拓展顺序没有要求。因此我们每次将 \(x\) 拓展到 \((x, x')\) 路径 \(h_i\) 最大值最小的 \(x'\) 上。记这个点为 \(f_{x}\),设最大值为 \(mx_x\),\(w_x = mx_x - h_x\)。
将 \(x\) 和 \(f_x\) 连边,会形成一棵内向树。那么我们的过程是每次将一个 \(w_x\) 最小的点移动到 \(f_x\) 上,并判断是否满足限制。堆维护这个过程即可。复杂度 \(O(nk \times \log n \times \log V)\)。
然后发现二分是不需要的,我们同时移动 \(S\) 和 \(T\) 中的棋子,每次移动 \(w + sum\) 较小一方的棋子,不断拓展最大值即可。 \(sum\) 为当前所有棋子权值和。
复杂度 \(O(nk \times \log n)\)
#include<bits/stdc++.h>
#define pb emplace_back
using namespace std;
const int N = 2010;
typedef long long LL;
int n, m, s[2][N], f[N], cnt;
LL h[N], w[N];
vector< int > E[N];
void dfs(int x, int fa, int p, LL mx) {
LL Mx = max(mx, h[x]);
if(h[x] < h[p] || (h[x] == h[p] && x < p)) {
if(f[p] == -1 || (f[p] != -1 && w[p] > Mx) || (f[p] != -1 && w[p] == Mx && f[p] > x)) {
f[p] = x; w[p] = Mx;
}
}
for(auto v : E[x]) {
if(v == fa) continue;
dfs(v, x, p, Mx);
}
}
struct node {
LL w; int p, x; // 移动权值, 编号, 位置
friend bool operator < (node a, node b) {
return ((a.w > b.w) || (a.w == b.w && a.p > b.p));
}
};
priority_queue< node > q[2];
LL lim[2], S[2];
node Tp[2];
LL Lim(int o) {
return max(lim[o], S[o] + w[Tp[o].x]);
}
LL cost(int o) { // 移动 o 的代价
return S[o] + w[Tp[o].x];
}
void Push(int x, int p, int o) {
if(f[x] != -1) q[o].push((node) {w[x], p, x});
}
void move(int o) {
Tp[o] = q[o].top();
lim[o] = Lim(o); // 更新 lim
q[o].pop();
int p = Tp[o].p, x = Tp[o].x;
S[o] -= h[x];
cnt -= (s[o][p] == s[o ^ 1][p]);
s[o][p] = f[x];
S[o] += h[f[x]];
cnt += (s[o][p] == s[o ^ 1][p]);
Push(f[x], p, o);
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i ++ ) scanf("%lld", &h[i]);
for(int i = 1; i < n; i ++ ) {
int u, v; scanf("%d%d", &u, &v);
E[u].pb(v); E[v].pb(u);
}
memset(f, -1, sizeof f);
for(int i = 1; i <= n; i ++ )
dfs(i, 0, i, 0);
for(int i = 1; i <= n; i ++ ) {
if(f[i] != -1) w[i] = w[i] - h[i];
}
cnt = 0;
scanf("%d", &m);
for(int i = 1; i <= m; i ++ ) {
scanf("%d%d", &s[0][i], &s[1][i]);
S[0] += h[s[0][i]];
S[1] += h[s[1][i]];
Push(s[0][i], i, 0);
Push(s[1][i], i, 1);
cnt += (s[0][i] == s[1][i]);
}
lim[0] = S[0], lim[1] = S[1];
while(cnt < m) {
if(q[0].empty()) move(1);
else if(q[1].empty()) move(0);
else {
Tp[0] = q[0].top(), Tp[1] = q[1].top();
if(cost(0) < cost(1)) move(0);// 移动 0
else move(1);
}
}
printf("%lld\n", max(lim[0], lim[1]));
return 0;
}

浙公网安备 33010602011771号