有根树为一个有向图,该有向图有一个特殊的顶点,称之为根,从根出发,存在唯一的有向路径到达图中的任意其他顶点。按照习惯,一般将有根树中的顶点称为结点。子树为有根树 T 的一个子图且该子图是一棵以 T 中某个结点为根的有根树。在有根树中,如果有一条边从结点 i 出发,到达结点 j,则将结点 i 称为结点 j 的父结点,将结点 j 称为结点 i 的子结点。将有根树中不存在子结点的结点称为叶结点。
在 算法3 中,每执行一次步骤(2)就会生成一棵有根树,令第 i 次生成的有根树为 K_i,将每棵有根树使用一个结点 i 来表示,其权值为 w_i = \lceil r(K_i) \rceil,则可以得到一棵新的有根树 T_w,最小化 W 值的问题可以转化为以下问题:从根结点开始,逐次删除结点,只有在删除父结点后才能删除子结点,第 i 次删除结点的代价为结点权值与删除次数序号的乘积,如何最小化删除所有结点的代价之和。
需要判断 w_b + w_c - 2w_a 的大小,将其转换一下,实际上就是要判断 \frac{w_b + w_c}{2} 和 w_a 的大小关系,也就是说,如果结点 b 和 c 的平均权值大于结点 a 的权值,则应该选择第二种删除顺序,即先删除 b 和 c,然后再删除 a,否则就应该先删除 a ,再删除 b 和 c。这提示我们,删除的优先顺序不是与单个结点的权值相关,而是与多个结点的平均权值有关。可以证明,由于删除的代价与删除的次序序号成线性关系,当结点数增多时,上述性质仍然成立(参考资料 \texttt{[2]})。那么问题转化为以下的问题:对于有根树中的每个结点 i,确定以结点 i 为根的具有最多结点的子树 T_i,该子树 T_i 具有最大的平均权值。按照平均权值进行贪心选择来删除结点,能够获得最小化的删除代价。而这个问题,实际上就是将第一次调用 算法3 得到的新的有根树 T_w 的结点权值设置为 a_i = w_i = \lceil r(K_i) \rceil, \ b_i = 1,从根结点开始再调用一次 算法2,只不过将 算法2 中的取最小值改成取最大值。
在确定了新的有根树 T_w 中所有结点 i 的 r_i 和 T_i 后,令 W 表示总代价,t 为当前的操作序号,则可以使用以下算法来确定 W 的最小值:
算法4
(1)初始时,置 W \leftarrow 0, \ t \leftarrow 1,将根结点的配对 (i, \ r_i) 送入堆 H;
(2)若 H = \varnothing,算法停止,否则取 H 中的一个配对 (j, \ r_j),该配对的第二个分量值最大;
(3)置 W \leftarrow W + t \times w_j, \ t \leftarrow t + 1;
(4)从 H 中删除配对 (j, \ r_j),对于 k \in S(T_j),将配对 (k, \ r_k) 送入 H,转步骤(2)。
最后,算法4 所得到的值 W 即为解。如果使用二叉堆来实现 算法4,易知其时间复杂度为 O(n\text{log}n)。
参考实现
#include <bits/stdc++.h>
using namespace std;
const int LENGTH = (1 << 20);
inline int nextChar()
{
static char buffer[LENGTH], *p = buffer, *end = buffer;
if (p == end) {
if ((end = buffer + fread(buffer, 1, LENGTH, stdin)) == buffer) return EOF;
p = buffer;
}
return *p++;
}
inline bool nextInt(int &x)
{
static char negative = 0, c = nextChar();
negative = 0, x = 0;
while ((c < '0' || c > '9') && c != '-') { if (c == EOF) return false; c = nextChar(); }
if (c == '-') { negative = 1; c = nextChar(); }
do x = (x << 3) + (x << 1) + c - '0'; while ((c = nextChar()) >= '0');
if (negative) x = -x;
return true;
}
const int MAXV = 1000010, MAXE = 2100010;
double ri[MAXV];
int n, visited[MAXV], MH[MAXV], head[MAXV], tot;
long long w[MAXV], ai[MAXV], bi[MAXV];
struct edge { int v, nxt; } edges[MAXE];
struct NODE { double ratio; int v, lc, rc, d; };
struct MH
{
NODE nd[MAXV];
int cnt = 0, maxHeap = 0;
inline bool cmp(double &a, double &b) { return maxHeap ? a < b : a > b; }
int merge(int x, int y)
{
if (!x || !y) return x | y;
if (cmp(nd[x].ratio, nd[y].ratio)) swap(x, y);
nd[x].rc = merge(nd[x].rc, y);
if (nd[nd[x].lc].d < nd[nd[x].rc].d) swap(nd[x].lc, nd[x].rc);
nd[x].d = nd[nd[x].rc].d + 1;
return x;
}
int get(double ratio, int v)
{
++cnt;
nd[cnt].ratio = ratio;
nd[cnt].v = v;
nd[cnt].lc = nd[cnt].rc = nd[cnt].d = 0;
return cnt;
}
int push(int x, int y) { return merge(x, y); }
NODE top(int x) { return nd[x]; }
int pop(int x) { return merge(nd[x].lc, nd[x].rc); }
}H;
void dfs(int u)
{
visited[u] = 1;
ri[u] = (double)ai[u] / bi[u];
if (head[u] == -1) return;
for (int i = head[u]; ~i; i = edges[i].nxt)
{
int v = edges[i].v;
if (!visited[v])
{
dfs(v);
int y = H.get(ri[v], v);
if (!MH[u]) MH[u] = y;
else MH[u] = H.push(MH[u], y);
}
}
while (MH[u])
{
NODE lt = H.top(MH[u]);
if (H.cmp(lt.ratio, ri[u])) break;
MH[u] = H.pop(MH[u]);
ai[u] += ai[lt.v], bi[u] += bi[lt.v];
ri[u] = (double)ai[u] / bi[u];
MH[u] = H.merge(MH[u], MH[lt.v]);
}
}
void add(int u, int v)
{
edges[tot].v = v;
edges[tot].nxt = head[u];
head[u] = tot++;
}
int main(int argc, char *argv[])
{
cin.tie(0), cout.tie(0), ios::sync_with_stdio(false);
nextInt(n);
for (int i = 1; i <= n; i++) head[i] = -1;
for (int i = 2, u; i <= n; i++)
{
nextInt(u);
add(u, i);
}
for (int i = 1, av; i <= n; i++)
{
nextInt(av);
ai[i] = av;
}
for (int i = 1, bv; i <= n; i++)
{
nextInt(bv);
bi[i] = bv;
}
int R = 1;
queue<int> q;
q.push(R);
while (!q.empty())
{
int u = q.front();
q.pop();
dfs(u);
w[u] = ai[u] = ceil(ri[u]), bi[u] = 1;
head[u] = -1;
while (MH[u])
{
NODE lt = H.top(MH[u]);
MH[u] = H.pop(MH[u]);
ai[lt.v] += ai[u];
add(u, lt.v);
q.push(lt.v);
}
}
H.cnt = 0;
H.maxHeap = 1;
for (int i = 1; i <= n; i++) visited[i] = 0;
dfs(R);
long long W = 0, t = 1;
priority_queue<pair<double, int>> pq;
pq.push(make_pair(ri[R], R));
while (!pq.empty())
{
pair<double, int> gt = pq.top();
pq.pop();
W += t * w[gt.second];
for (int i = head[gt.second]; ~i; i = edges[i].nxt)
{
int v = edges[i].v;
pq.push(make_pair(ri[v], v));
}
t++;
}
cout << W << '\n';
return 0;
}
参考资料
\texttt{[1] Galil Z. Applications of efficient mergeable heaps for optimization problems on trees [J]. }\texttt{\ \ \ \ Acta Informatica, 1980, 13: 53-58.}\texttt{[2] Horn W A. Single-machine job sequencing with treelike precedence ordering and linear delay penalties [J].}\texttt{\ \ \ \ SIAM Journal on Applied Mathematics, 1972, 23(2): 189-202.}