Color a tree
题目描述
给一棵 nnn 个点的树,每个点有一个点权 cic_ici,求一长度为 nnn 的排列 ppp,要求父亲排在儿子前面(原题意:求染色顺序,父亲染色后儿子才能染色),最小化
S=∑i=1nicpi.
S=\sum_{i=1}^nic_{p_i}.
S=i=1∑nicpi.
题解
注意:为了叙述简洁,以下可能用“应该”、“一定”等说法替代“没有其它解比这样做更优”或“存在一种最优解使得”,用“更优”替代“不会更差”的意思等。
考虑逐个给点染色的过程,设还没有染色的点的集合是 V0V_0V0,并记 V0V_0V0 中当前可以染色的点的集合为 V1V_1V1,即 V1={u∣father(u)∉V0}V_1 = \{u \mid \mathrm{father}(u) \notin V_0\}V1={u∣father(u)∈/V0}。初始时,V1V_1V1 只包含根节点。
设 uuu 为 V0V_0V0 中点权最大的点,即 u=arg maxv∈V0cvu=\argmax_{v \in V_0} c_vu=argmaxv∈V0cv。如果 u∈V1u\in V_1u∈V1,不难得出此时应该直接给 uuu 涂色;否则,设 uuu 的父亲为 fff,那么 fff 染色后,下一个染色的一定是 uuu。
由此,可以考虑将 u,fu, fu,f 合并,绑定为一个新节点,设为 v\bm{v}v(用粗体表示合并点)。当选到 v\bm{v}v 时,等效于先选 fff 再选 uuu。合并时要对应处理树上亲子关系:把 uuu 的所有儿子、以及 fff 除 uuu 外 的儿子全部置为 v\bm{v}v 的儿子。
这样进行点的合并后,树上的每个点都可以看成一些原始的树上的点的集合,对于合并点 u\bm{u}u 记其中所有点权和为 su=∑v∈ucvs_u=\sum_{v\in \bm{u}}c_vsu=∑v∈ucv。
对于合并点要重新考虑贪心策略:设 u,v\bm{u}, \bm{v}u,v 是时刻 ttt 时可以选的两个点,根据目标函数的定义,不难算出先选 u\bm{u}u 与先选 v\bm{v}v 的答案差异为 Δ=sv∣u∣−su∣v∣\Delta = s_v |\bm{u}| - s_u |\bm{v}|Δ=sv∣u∣−su∣v∣。为了得到先选 uuu 更优的条件,令 Δ<0\Delta<0Δ<0,可得
su∣u∣>sv∣v∣.
\frac{s_u}{\left|\bm{u}\right|}>\frac{s_v}{\left|\bm{v}\right|}.
∣u∣su>∣v∣sv.因此,针对合并点的贪心策略是优先选点权平均值大者。
如此迭代 n−1n - 1n−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;
}
本文详细解析了Coloratree算法,一种针对树形结构数据的优化算法,通过动态合并节点来最小化特定函数的值。文章深入探讨了算法的实现细节,包括节点权重的计算、堆的使用以及并查集的数据结构应用。

647

被折叠的 条评论
为什么被折叠?



