【多解 · 代码超详解】POJ 3253 Fence Repair 栅栏修复【贪心 · 思维】

一、题目描述

农夫 John(FJ)想对围着牧场的一小段栅栏作修补。他测量了栅栏长度,发现一共需要 N(1 ≤ N ≤ 20000)块木板,第 i 块木板的长度是 Li 个单位(1 ≤ Li ≤ 50000)。他买了一块长木板,其长度正好足够切成 N 块这样的木板。忽略每次锯木板的损失。
FJ 发现他并没有锯子,于是他带着这块长木板找到 Don 的农场去问她能不能借一把锯子。Don 并没有把锯子借给 FJ 而是向 FJ 收费,切一段木板一次的花销是木板的总长度。例如切一段 21 单位长的木板一次的花费是 21 单位。Don 让 FJ 决定且木板的顺序。请你帮助 FJ 计算把木板切成要求的 N 段需要的最小费用。切木板有很多种方式,不同切割方式会在切割过程中产生不同的中间长度,导致总花费不同。
第一行输入 N ,接下来 N 行输入每段木板的长度要求。

二、算法分析说明与代码编写指导

法一

将一段木板及其切开后的木板继续切,每次切都会把一段木板分成两段。我们从最终结果开始逆推,发现不断取两段木板拼回,就可以恢复到上一步的状态,并推知这一次切割的花销在数值上等于拼接好的木板的长度。拼接总共(N - 1)次,就将全部 N 段木板恢复成了一块,同时也可以将总花销统计出来。如果每次都取两段最短的拼接,总的花销就最小。这也可以理解成:切的时候尽量将短的木板多切几次,因为如果通过切割长木板来得到某些最终要求得到的短木板,每次切的开销就更大,因为被切割的木板更长。
本题如果采用 int 类型会导致 Wrong Answer ,原因是花销的总和可以大于木板的总长度。比如样例输入:

3
8
5
8

输出结果是:

34

而木板的总长是 21 。
理论上木板的最大长度可以达到 20000 × 50000 = 1 000 000 000 ,总的花销应该远大于十亿,所以为了防止溢出,最佳的数据类型应该是 unsigned long long 。不过本题采用 unsigned int 作数据类型也可以 AC ,原因自然是数据比较水。
findminterm 函数用于以 O(n) 的复杂度查找所有木板长度里最短的两项,总复杂度为 n方。如果直接用 std::sort 排序,由于复杂度是 nlogn ,而每次合并以后都要重新排序,总的复杂度大约为 n^2 * logn ,会导致超时。
记 s0 和 s1 分别是最小长度和次小长度的下标,每次将两块最短木板合并成一块后,数组中有一个位置是空出来的,把这个标记为 2^32 - 1 (如果记为 2 的 64 次方减 1 会超时,本题限时 2000 ms)防止下次查找最小项和次小项时找到已经拼起来的木板的一部分,导致错误结果。
当拼到只剩 1 块长木板时,循环结束,输出答案。

法二

优先队列。把全部的长度都读到优先队列中去(小到大排序),然后取出最小的两截板合并为一截再丢回队列里,同时累加花销,当队列只剩下一个数就代表已经还原成原来的木板,此时输出答案。优先队列用堆(heap)实现,增删改的复杂度都是 O(logn) 。

三、AC 代码

(844 ms,数据类型用 unsigned long long 则是 1797 ms ,本题数据比较水,unsigned 可以过)

#include
#include
#pragma warning(disable:4996)
unsigned n, n0, l[20000], s0 , s1, c = 0;
inline void findminterm() {
	s0 = 0, s1 = 1; if (l[s0] > l[s1])std::swap(l[s0], l[s1]);
	for (unsigned i = 2; i < n; ++i) {
		if (l[i] < l[s0]) { s1 = s0, s0 = i; }
		else if (l[i] < l[s1])s1 = i;
	}
}
int main() {
	scanf("%u", &n), n0 = n;
	for (unsigned i = 0; i < n; ++i)scanf("%u", &l[i]);
	for (;;) {
		findminterm(), l[s1] += l[s0], c += l[s1], l[s0] = 0xffffffff, --n0;
		if (n0 == 1)break;
	}
	printf("%u\n", c);
	return 0;
}

(16 ~ 32 ms ,本方法无论让所有整数为 unsigned 或 unsigned long long 类型,时间上的差别都不大)

#include
#include
#include
#pragma warning(disable:4996)
using namespace std;
unsigned n, l1, l2, a = 0; priority_queue<unsigned, vector<unsigned>, greater<unsigned>> q;
int main() {
	scanf("%u", &n);
	for (unsigned i = 0; i < n; ++i) { scanf("%u", &l1); q.push(l1); }
	while (q.size() > 1) { l1 = q.top(), q.pop(), l2 = q.top(), q.pop(), a += l1 + l2, q.push(l1 + l2); }
	printf("%u\n", a);
	return 0;
}

你可能感兴趣的:(ACM-ICPC)