给一棵 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。
设 u u u为 V 0 V_0 V0中点权最大的点,即 c u = min { c v ∣ v ∈ V 0 } c_u=\min\{c_v\mid v\in V_0\} cu=min{cv∣v∈V0}。
如果 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 v v,表示选了 f f f 后必选 u u u。同时,把 u u u 的所有儿子以及 f f f 的除 u u u的儿子全部置为 v v v 的儿子。
若在 t t t 时刻染色 v v v,对答案的贡献应为 t c f + ( t + 1 ) c u = t ( c f + c u ) + c u t c_f + (t+1)c_u = t(c_f +c_u) + c_u tcf+(t+1)cu=t(cf+cu)+cu,因此可以考虑在合并时边将答案增加 c u c_u cu,在 t t t 时刻涂黑 v v v 时直接贡献 v v v 的两个点的权值之和与 t t t的乘积即可。
下面考察如何设置新点 v v v 的点权。
现在可以把树上的每个点都可以看成一定的点的集合。对于一个点 u u u,设其对应点集为 V u V_u Vu,其中的点权和为
s u = ∑ v ∈ V u c v s_u=\sum_{v\in V_u}c_v su=v∈Vu∑cv
设 u , v u,v u,v是当前可以涂的两个点,考察 t t t时刻时,先后涂黑的顺序的差异
Δ = ( s u t + s v ( t + ∣ V u ∣ ) ) − ( s v t + s u ( t + ∣ V v ∣ ) = s v ∣ V u ∣ − s u ∣ V v ∣ \Delta=(s_u t + s_v (t + \left| V_u\right|))-(s_v t + s_u (t + \left| V_v\right|) = s_v\left| V_u\right|- s_u \left| V_v\right| Δ=(sut+sv(t+∣Vu∣))−(svt+su(t+∣Vv∣)=sv∣Vu∣−su∣Vv∣
若先涂 u u u,则令 Δ < 0 \Delta<0 Δ<0,得到
s u ∣ V u ∣ > s v ∣ V v ∣ \frac{s_u}{\left|V_u\right|}>\frac{s_v}{\left|V_v\right|} ∣Vu∣su>∣Vv∣sv
因此可以把新点权设为点集的点权和的算数平均值。
然后和上面一样的讨论,每次找到点权的平均值最大的点 u u u,若父亲 f f f已涂色则直接涂黑,否则合并,并先将答案累加 ∣ V f ∣ s u |V_f| s_u ∣Vf∣su,再将 u , f u,f u,f合并。直到将所有点涂黑。
#include
#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;
}