【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

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{cvvV0}

如果 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 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=vVucv
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)=svVusuVv
若先涂 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|} Vusu>Vvsv
因此可以把新点权设为点集的点权和的算数平均值。

然后和上面一样的讨论,每次找到点权的平均值最大的点 u u u,若父亲 f f f已涂色则直接涂黑,否则合并,并先将答案累加 ∣ V f ∣ s u |V_f| s_u Vfsu,再将 u , f u,f u,f合并。直到将所有点涂黑。

代码

#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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值