POJ-2054 Color a Tree

目录

题目

题目描述

翻译:

输入

 翻译:

输出

翻译:

思路解析

AC代码


题目

题目描述

翻译:

Bob对树十分感兴趣。树是一种有向图,其中有一个特殊的节点被称为树的“根”。并且“根”与任意节点之间的可达路径是唯一的。

Bob想要给树的所有节点进行上色。一颗树拥有N个节点,这些节点编号为1,2,....,N。假设Bob为一个节点上色需要1个单元时间,并且Bob上色的操作是逐个进行的,完成一个节点的上色后,可以为另一个节点上色。除此之外,每一个节点被上色之前,其父节点已被上色。显而易见的,Bob的一次尝试有且只能对“根”节点进行操作。

每一个节点拥有一个“上色成本”。“上色成本”依赖于编号i个节点的权值和其完成的时间。例如编号i的节点权值为Ci并且在Fi时间完成,那么它的上色成为Ci*Fi。

(中间给了一个例子的答案,不翻译了...)

请你给出Bob给树上色的可行最小代价。

输入

 翻译:

输入由一系列的测试样例构成。每一组测试样例的第一行由两个数字N和R,其中N代表树的节点个数,同时R代表根节点的编号。这第二行包含N个整数,第i个整数是第i号个节点的权值。接下来N-1行个由两个数组成,它们分别代表两个节点V1和V2。其中V1节点是V2节点的父亲。没有任何一条边被连接两次,并且所有边都被表达

特别地,有一组样例为N = 0, R = 0.这是结束检测的标志。

输出

翻译:

这就不用翻了吧...就是给出答案,每一组答案占一行。 

思路解析

看到 Fi*Ci,我们很容易想到“逐步调整法(微扰技术)”,或者说是排序不等式(逆序积最小)。哦~以上说法只是专业一些,其实人们的直觉已经足够看到这一点了。

但是这样子的“贪心”是错误的。因为我们忽略了限制条件--每一个节点被上色之前,其父节点必然已经被上色。所以,我们很容易知道上述的“贪心”是错误的。当然,你也可以举出一个反例。例如构造一个树使得小权值节点拥有大权值子节点。但是究其本源,这个反例在说明限制条件的作用。

但是这样的想法不是毫无价值的。我们会发现,最大权值的节点,在其父节点被上色后应该立马进行上色。这样才符合我们排序不等式。

至此我们得到一个全新的看法:将最大权值节点和其父节点进行合并。因为操作是连续的。

但是问题来了,合并后的值是什么样的

假如我们的现在由三个节点需要上色,它们的权值分别为z,x,y。其中x,y的上色是关联的。x上色完,马上对y上色。

那么,我们只有两种情况:

z先上色:代价 = z + 2 * x + 3 * y

x,y先上色: 代价 = x + 2 * y + 3 * z

考虑到我们想知道那个代价更小,换句话说我们只想知道两种情况的大小关系。所以我们可以适当变形,判定大小关系。不妨在两式同 + (z - y)

那么有 2*z + 2*x + 2*y 和 x + y + 4 * z 的大小关系。再移项得到 (x + y) / 2 和 z 的大小关系

我们忽而想到,合并之后的值与“平均值”的关系。当然我们目前不能排除是 / 2 的关系。

所以我们再来一般化。

假定我们现在又M个节点的上色是关联的,N个节点的上色是关联的。同上推理:

W1 = 先对M个点上色, W2 = 先对N个节点上色

从以上推导,我们正式敲定合并后的权值是 “平均值”

因为,我们每次都在贪心选取最大均值节点。所以可以使用大顶堆维护

但是,我们以上的操作只能知道上色次序,所以我们需要记录上色次序,并按照次序求值就能获取答案。这里需要注意树的序列化,这道题目中子序列应当添加再父序列之后,小均值节点序列应该添加在大均值序列之后。这样的添加序列只需要知道链表的头尾即可。这里需要数组模拟链表。

AC代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <queue>
using namespace std;
const int N = 1e3 + 5;
int n, r, fa[N], nxt[N], lst[N], num[N];
double c[N], d[N], tot[N];
bool vis[N];
typedef pair<double, int> PII;
int main() {
  while (scanf("%d%d", &n, &r) && n && r) {//多组数据
	priority_queue <PII, vector<PII>, less<PII> > q;//大顶堆,默认就是大顶堆
	for (int i = 1; i <= n; i++) {//O(n)
	  scanf("%lf", &c[i]);
	  nxt[i] = lst[i] = i;
	  num[i] = 1;
	  tot[i] = c[i];
	  if (i != r) q.push({ c[i], i });//将非根节点入队
	}
	memcpy(d, c, sizeof d);//拷贝一份原数据,最后求知用
	for (int i = 1; i < n; i++) {//O(n)
	  int a, b;
	  scanf("%d%d", &a, &b);
	  fa[b] = a;
	}
	memset(vis, 0, sizeof vis);//每个节点只能上色一次
	for (int i = 1; i < n; i++) {
	  int p;
	  p = q.top().second; q.pop();
	  while (vis[p] || p == r) {
		p = q.top().second;
		q.pop();
	  }//取出等效权值最大且合法的结点
	  int f = fa[p];
	  while (vis[f]) fa[p] = f = fa[f];

      //记录操作次序
      //小均值节点应该后上色,所以加入到当前序列的最末端,并更新父亲节点的序列末端值
	  nxt[lst[f]] = p;
	  lst[f] = lst[p];

	  num[f] += num[p], tot[f] += tot[p];
	  c[f] = tot[f] / num[f];//合并
	  vis[p] = 1;
	  q.push({ c[f], f });//将更新的结果入队
	}
	int ans = 0;
	for (int i = 1; i <= n; i++) {
	  ans += i * d[r];
	  r = nxt[r];
	}//计算代价
	printf("%d\n", ans);
  }

  return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值