POI2014 洛谷P3574 FarmCraft 题解


题目链接

https://www.luogu.com.cn/problem/P3574

题目大意

给定一棵树,每个结点有一个村庄,小明从 \(1\) 号结点出发,每条边仅往返各一次,他每次到达一个村庄,就放下一台电脑,并立即向其他村庄行进。小明走每条边需要花费一个单位时间。每个村庄收到电脑后,会立即安装一款软件,第 \(i\) 个村庄安装软件需要花费 \(c[i]\) 的时间。小明最终返回 \(1\) 号结点后,才会给自己安装软件。问所有村庄都安装完软件最少需要多长时间。

输入格式

一个 \(n\),表示结点个数;
\(n\) 个数,表示每个结点安装软件需要花费的时间
\(n-1\) 行,每行两个数,表示每条边

输出格式

就是最少时间

输入样例

6
1 8 9 6 3 2
1 3
2 3
3 4
4 5
4 6

输出样例

11

分析

我们考虑这个题目的答案的构成。需要最少的时间,一部分是他走完所有村庄的时间的开销 \(+\) 他自己安装软件;
另一部分是返回到 \(1\) 号结点后等待其他村庄安装完成的时间,两者取最大值即可。
对于后者来说,我们必须先求出返回每棵子树的根结点后,还需要等待的时间,才能退出最终结果。
因此,我们尅定义 \(f[i]\) 表示返回以 \(i\) 为根的子树的根结点 \(i\),要完成安装,还需要等待的时间。

  • 对于以 \(i(i>1)\) 号结点为根的子树,从 \(i\) 出发走完并返回 \(i\) 所需要的时间为 \(2\times (size[i]-1)\),其中\(size[i]\) 表示子树 \(i\) 总共的结点个数。
  • 那么只考虑自己安装软件的情况下,需要等待的时间为 \(a[i]-2\times(size[i]-1)\),如果小于 \(0\),说明返回后软件已经安装完成,所以对结果的真正的贡献为 \(\max(0, a[i]-2\times(size[i]-1))\)
  • 当然可能他的子孙结点的安装软件速度很慢,有可能大于上述的贡献值,因此我们还要考虑子结点的贡献。
  • 由于 \(i\) 每个孩子结点都会有一个等待时间,因此我们在走的时候,优先走等待时间长的,把等待的时间消耗在遍历其他孩子的路上,这样才能使得结果最优,证明还没想好,稍后附上……
    我们在求得每个孩子 \(son[j]\) 的最少等待时间后,按照等待时间将序排列,依次更新父结点的等待时间,递推式如下:
    假设在处理孩子 \(son[j]\) 之前,需要等待的时间为 \(f[i]\),此时再来一个孩子 \(son[j]\),那么等待时间需要更新为 \(\max(f[i]-size[son[j]], f[son[j]]-1, 0)\),即原来的等待时间减去遍历 \(son[j]\) 消耗的时间,与 \(son[j]\) 提供的等待时间(减 \(1\) 是要从孩子结点回到父结点)的最大值,同时,这里面也包含了上面“只考虑自己安装软件”的情况。
    因此,我们综合一下上面的式子:
    • 起初刚到 \(i\) 的时候,要安装软件,所以 \(f[i] = a[i]\)
    • \(f[i]=max(f[i]-size[son[j]], f[son[j]]-1, 0)\),其中第一项为 \(i\) 自身安装的贡献,第二项为 \(son[j]\) 的贡献,第三项理所当然最少的等待时间为 \(0\)
  • 最终的答案特殊处理一下,因为 \(1\) 号结点最后安装,所以\(ans=\max(a[1], f[1])+2\times (size[1]-1)\)
    注意,这里的 \(a[1]\) 最开始改成了 \(0\),因为开始 \(1\) 不安装,最后求解的时候再将 \(a[1]\) 改回原值

部分代码

void dfs(int now, int fa) { // 返回i为根的子树,所有都安装完最小等待时间
	vector vet;
	size[now] = 1;
	f[now] = a[now];
	for (int i = head[now]; i; i = e[i].next) {
		int to = e[i].to;
		if (to == fa) continue;
		dfs(to, now);
		size[now] += size[to];
		vet.push_back(Node(to, f[to]));
	}
	sort(vet.begin(), vet.end()); // 对孩子的等待时间降序排列
	for (int i = 0; i < vet.size(); ++i) {
		int son = vet[i].id;
		// f[son]-1表示son返回父结点后还需要等待的时间
		// f[now]-(size[son]<<1)表示从now跑完son这棵子树返回后还需要等待的时间
		f[now] = max(max(f[son]-1, (ll)0), f[now]-(size[son]<<1));
	}
}

// 最后的答案,x 为原来 1 安装的耗时
printf("%lld", max(f[1], (ll)x)+(size[1]-1<<1));

相关