Color a tree
题目描述
给一棵
n
n
n 个点的树,每个点有一个点权
c
i
c_i
ci,求一长度为
n
n
n 的排列
p
p
p,要求父亲排在儿子前面(原题意:求染色顺序,父亲染色后儿子才能染色),最小化
S
=
∑
i
=
1
n
i
c
p
i
.
S=\sum_{i=1}^nic_{p_i}.
S=i=1∑nicpi.
题解
注意:为了叙述简洁,以下可能用“应该”、“一定”等说法替代“没有其它解比这样做更优”或“存在一种最优解使得”,用“更优”替代“不会更差”的意思等。
考虑逐个给点染色的过程,设还没有染色的点的集合是 V 0 V_0 V0,并记 V 0 V_0 V0 中当前可以染色的点的集合为 V 1 V_1 V1,即 V 1 = { u ∣ f a t h e r ( u ) ∉ V 0 } V_1 = \{u \mid \mathrm{father}(u) \notin V_0\} V1={u∣father(u)∈/V0}。初始时, V 1 V_1 V1 只包含根节点。
设 u u u 为 V 0 V_0 V0 中点权最大的点,即 u = arg max v ∈ V 0 c v u=\argmax_{v \in V_0} c_v u=argmaxv∈V0cv。如果 u ∈ V 1 u\in V_1 u∈V1,不难得出此时应该直接给 u u u 涂色;否则,设 u u u 的父亲为 f f f,那么 f f f 染色后,下一个染色的一定是 u u u。
由此,可以考虑将 u , f u, f u,f 合并,绑定为一个新节点,设为 v \bm{v} v(用粗体表示合并点)。当选到 v \bm{v} v 时,等效于先选 f f f 再选 u u u。合并时要对应处理树上亲子关系:把 u u u 的所有儿子、以及 f f f 除 u u u 外 的儿子全部置为 v \bm{v} v 的儿子。
这样进行点的合并后,树上的每个点都可以看成一些原始的树上的点的集合,对于合并点 u \bm{u} u 记其中所有点权和为 s u = ∑ v ∈ u c v s_u=\sum_{v\in \bm{u}}c_v su=∑v∈ucv。
对于合并点要重新考虑贪心策略:设
u
,
v
\bm{u}, \bm{v}
u,v 是时刻
t
t
t 时可以选的两个点,根据目标函数的定义,不难算出先选
u
\bm{u}
u 与先选
v
\bm{v}
v 的答案差异为
Δ
=
s
v
∣
u
∣
−
s
u
∣
v
∣
\Delta = s_v |\bm{u}| - s_u |\bm{v}|
Δ=sv∣u∣−su∣v∣。为了得到先选
u
u
u 更优的条件,令
Δ
<
0
\Delta<0
Δ<0,可得
s
u
∣
u
∣
>
s
v
∣
v
∣
.
\frac{s_u}{\left|\bm{u}\right|}>\frac{s_v}{\left|\bm{v}\right|}.
∣u∣su>∣v∣sv.因此,针对合并点的贪心策略是优先选点权平均值大者。
如此迭代 n − 1 n - 1 n−1 次便能确定选点顺序,然后就能根据目标函数的定义算出答案了。
代码
#include <bits/stdc++.h>
#define maxn 100000
using namespace std;
struct UnionFindSet {
int f[maxn + 10];
void setf(int u, int f)
{
this->f[u] = f;
}
int find(int x)
{
return f[x] == x ? x : f[x] = find(f[x]);
}
};
UnionFindSet ufs;
int fa[maxn + 10];
typedef long long LL;
struct Node {
int u;
LL sum;
int size;
int *pos;
Node(int u = 0, LL sum = 0, int size = 0, int* pos = nullptr) : u(u), sum(sum), size(size), pos(pos) {}
bool operator < (const Node &a) const
{
return sum * a.size < a.sum * size;
}
};
struct Heap {
Node h[maxn + 10];
int size;
void hswap(int a, int b)
{
swap(h[a], h[b]);
swap(*h[a].pos, *h[b].pos);
}
void up(int o)
{
while (o > 1) {
int f = o >> 1;
if (h[f] < h[o]) hswap(f, o);
else break;
o = f;
}
}
void down(int o)
{
for (;;) {
int lc = o << 1;
if (lc > size) break;
if (lc == size) {
if (h[o] < h[lc]) hswap(lc, o);
break;
} else {
int rc = lc | 1, t = lc;
if (h[t] < h[rc]) t = rc;
if (h[o] < h[t]) hswap(t, o);
else break;
o = t;
}
}
}
void push(const Node& v)
{
h[++size] = v;
*v.pos = size;
up(size);
}
void pop(int pos)
{
hswap(pos, size--);
up(pos);
down(pos);
}
void update(int o, LL sum, int size)
{
h[o].sum = sum;
h[o].size = size;
up(o);
down(o);
}
const Node& getNode(int pos)
{
return h[pos];
}
};
int c[maxn + 10];
struct Edge {
int u, v, next;
}edge[2 * maxn + 10];
int head[maxn + 10], pp;
void adde(int u, int v)
{
edge[++pp] = (Edge){u, v, head[u]};
head[u] = pp;
}
void dfs(int u)
{
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].v;
if (v != fa[u]) {
fa[v] = u;
dfs(v);
}
}
}
int pos[maxn + 10];
bool vis[maxn + 10];
Heap H;
int main()
{
#ifndef ONLINE_JUDGE
freopen("input.txt", "r", stdin);
#endif
for(;;) {
int n, root;
cin >> n >> root;
if (n == 0) break;
for (int i = 1; i <= n; i++) head[i] = fa[i] = 0;
pp = 0;
for (int i = 1; i <= n; i++) scanf("%d", c + i);
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
adde(u, v);
adde(v, u);
}
dfs(root);
for (int i = 1; i <= n; i++) H.push(Node(i, c[i], 1, &pos[i]));
for (int i = 1; i <= n; i++) ufs.setf(i, i);
LL ans = 0;
for (int i = 1; i <= n; i++) vis[i] = 0;
for (int i = 1, t = 1; i <= n; i++) {
Node cur = H.getNode(1);
H.pop(1);
int u = cur.u, f = fa[u];
if (f != 0) f = ufs.find(f);
if (f == 0 || vis[f]) {
vis[u] = 1;
ans += t * cur.sum;
t += cur.size;
} else {
Node fa = H.getNode(pos[f]);
ans += cur.sum * fa.size;
ufs.setf(u, f);
H.update(pos[f], fa.sum + cur.sum, fa.size + cur.size);
}
}
cout << ans << endl;
}
return 0;
}