【NOI2015】荷马史诗

显然一个Huffman树,然后改成 \(K\) 叉的就好。但是我不会Huffman树,于是学学拓宽知识面。

首先这个 \(K\) 叉树就是一个类似字典树的东西,但是不对应目标串的点都是非叶结点(不然不满足前缀关系的限制)

容易证明,每个点带 \(K - 1\) 个叶子是最优的。

考虑计算总权值时的 深度乘以权值 改为 它到根的链上都加上它的权值,那么 \(\sum dep_i \times val_i = \sum_{u \notin leaves} v_i\)

于是让权值大的尽量贡献最少,那么就要出现在深度浅的地方。

因此考虑自底向上构建。考虑维护一个堆,每次取出前 \(K\) 小的,合并成一个点,然后扔进堆里。合并的时候维护权值(子节点的和)。直到合并成单个点。显然,权值越大的贡献次数越小。于是这样贪心是正确的(然而我并不会严格的证明)。

然后就建出一个 Huffman树了。但是对于第二问我们还是要得出深度最小值,于是加入第二关键字深度,合并的时候注意维护就行了。

还有因为每次减少 \(K - 1\) 个,所以可能最后剩下不是 \(1\) 个,所以,补上几个 \(0\) 使得 \(n \equiv 1\ (\mod K - 1)\),显然不会对答案产生影响。

我挂在了补 \(0\) 以及 第二关键字上,WA了两发。

#include <bits/stdc++.h>

typedef long long LL;
typedef std::pair<LL, int> P;
std::priority_queue<P, std::vector<P>, std::greater<P> > q;
int n, K; LL t;
int main() {
	std::ios_base::sync_with_stdio(false), std::cin.tie(0);
	std::cin >> n >> K;
	for (int i = 1; i <= n; ++i) std::cin >> t, q.emplace(t, 0);
	while (n % (K - 1) != 1 % (K - 1)) ++n, q.emplace(0, 0);
	LL ans = 0;
	while (q.size() != 1) {
		LL tv = 0; int dx = 0;
		for (int i = 1; i <= K; ++i)
			tv += q.top().first, dx = std::max(dx, q.top().second), q.pop();
		q.emplace(tv, dx + 1); ans += tv;
	}
	std::cout << ans << std::endl << q.top().second << std::endl;
	return 0;
}
posted @ 2019-09-25 20:39  daklqw  阅读(137)  评论(0编辑  收藏  举报