M05333: Fence Repair
huffman, greedy, http://cs101.openjudge.cn/practice/05333
Farmer John wants to repair a small length of the fence around the pasture. He measures the fence and finds that he needs N (1 ≤ N ≤ 20,000) planks of wood, each having some integer length Li (1 ≤ Li ≤ 50,000) units. He then purchases a single long board just long enough to saw into the N planks (i.e., whose length is the sum of the lengths Li). FJ is ignoring the "kerf", the extra length lost to sawdust when a sawcut is made; you should ignore it, too. FJ sadly realizes that he doesn't own a saw with which to cut the wood, so he mosies over to Farmer Don's Farm with this long board and politely asks if he may borrow a saw.
Farmer Don, a closet capitalist, doesn't lend FJ a saw but instead offers to charge Farmer John for each of the N-1 cuts in the plank. The charge to cut a piece of wood is exactly equal to its length. Cutting a plank of length 21 costs 21 cents.
Farmer Don then lets Farmer John decide the order and locations to cut the plank. Help Farmer John determine the minimum amount of money he can spend to create the N planks. FJ knows that he can cut the board in various different orders which will result in different charges since the resulting intermediate planks are of different lengths.
输入
Line 1: One integer N, the number of planks Lines 2..N+1: Each line contains a single integer describing the length of a needed plank
输出
Line 1: One integer: the minimum amount of money he must spend to make N-1 cuts
样例输入
3
8
5
8样例输出
34提示
He wants to cut a board of length 21 into pieces of lengths 8, 5, and 8.
The original board measures 8+5+8=21. The first cut will cost 21, and should be used to cut the board into pieces measuring 13 and 8. The second cut will cost 13, and should be used to cut the 13 into 8 and 5. This would cost 21+13=34. If the 21 was cut into 16 and 5 instead, the second cut would cost 16 for a total of 37 (which is more than 34).
来源:USACO 2006 November Gold
与 18164: 剪绳子 一样。http://cs101.openjudge.cn/practice/18164/
import heapq
def minimum_cost(planks):
heapq.heapify(planks) # 将木板列表转换为最小堆
total_cost = 0
while len(planks) > 1:
# 取出最短的两块木板
shortest1 = heapq.heappop(planks)
shortest2 = heapq.heappop(planks)
# 计算切割的成本,并将切割后得到的木板长度加入堆
cost = shortest1 + shortest2
total_cost += cost
heapq.heappush(planks, cost)
return total_cost
# 读取输入
n = int(input())
planks = []
for _ in range(n):
length = int(input())
planks.append(length)
# 调用函数计算最小成本
result = minimum_cost(planks)
# 输出结果
print(result)解题思路:可以参看Huffman编码。
由于木板的切割顺序不确定,自由度很高,这个题目貌似很难人手。但是其实可以用略微奇特的贪心法来求解。首先,切割的方法可以参见二叉树来描述。每一个叶子节点就对应了切割出的一块块木板。叶子节点的深度就对应了为了得到对应木板所需的切割次数,开销的合计就是各叶子节点的 木板的长度 x 节点的深度 的总和。
于是,此时的最佳切割方法首先应该具有如下性质:
最短的板与次短的板的节点应当是兄弟节点。对于最优解来讲,最短的板应当是深度最大的叶子节点之一。所以与这个叶子节点同一深度的兄弟节点一定存在,并且由于同样是最深的叶子节点,所以应该对应于次短的板。
不妨将L~i~按照大小顺序排列,那么最短的板应该是L~1~,而次短的则是L~2~。如果它们在二叉树中是兄弟节点,就意味着它们是从一块长度为(L~1~+L~2~)的板切割得来的。由于切割顺序是自由的,不妨当作是最后被切割。这样一来,在这次切割前就有 (L~1~+L~2~), L~3~, L~4~, ..., L~N~ 这样的JN-1块木板存在。与以上讨论的方式相同,递归地将这N-1块木板的问题求解,就可以求出整个问题的答案。这样实现的话,虽然复杂度是0(N^2^), 对于题目的输入规模来说,已经足以在时间限制内通过了。
由于只需从板的集合里取出最短的两块,并且把长度为两块板长度之和的板加入集合中即可,因此如果使用优先队列就可以高效地实现。一共需要进行O(N)次O(logN)的操作,因此总的时间复杂度是〇(NlogN)。
import sys
try: fin = open('test.in','r').readline
except: fin = sys.stdin.readline
n = int(fin())
import heapq
a = []
for _ in range(n):
a.append(int(input()))
heapq.heapify(a)
ans = 0
for i in range(n-1):
x = heapq.heappop(a)
y = heapq.heappop(a)
z = x + y
heapq.heappush(a, z)
ans += z
print(ans)如果用朴素的方法实现的话,时间复杂度是O(N^2^)。
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int MAX_N = 20000;
int L[MAX_N+1];
int N;
void solve(){
ll ans = 0;
while (N>1) {
int mii1 = 0, mii2 = 1;
if (L[mii1] > L[mii2]) swap(mii1, mii2);
for (int i = 2; i < N; i++) {
if (L[i] < L[mii1]) {
mii2 = mii1;
mii1 = i;
}
else if (L[i] < L[mii2]) {
mii2 = i;
}
}
int t = L[mii1] + L[mii2];
ans += t;
if (mii1 == N - 1) swap(mii1, mii2);
L[mii1] = t;
L[mii2] = L[N - 1];
N--;
}
printf("%lld\n", ans);
}
int main()
{
cin >> N;
for (int i = 0; i<N; i++)
cin >> L[i];
solve();
return 0;
}