【多解 · 代码超详解】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<cstdio>
#include<algorithm>
#pragma warning(disable:4996)
unsigned n, n0, l[2000
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值