【hdu1055】【贪心】color a tree

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=1nicpi.

题解

注意:为了叙述简洁,以下可能用“应该”、“一定”等说法替代“没有其它解比这样做更优”或“存在一种最优解使得”,用“更优”替代“不会更差”的意思等。

考虑逐个给点染色的过程,设还没有染色的点的集合是 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={ufather(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=argmaxvV0cv。如果 u ∈ V 1 u\in V_1 uV1,不难得出此时应该直接给 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=vucv

对于合并点要重新考虑贪心策略:设 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}| Δ=svusuv。为了得到先选 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|}. usu>vsv.因此,针对合并点的贪心策略是优先选点权平均值大者。

如此迭代 n − 1 n - 1 n1 次便能确定选点顺序,然后就能根据目标函数的定义算出答案了。

代码

#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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值