这一节的主题是贪心和枚举。个人认为不剪枝的叫枚举,剪枝的叫搜索。
适当的枚举是许多优秀的解法中必不可少的环节,通常,枚举的东西可以分为这样几类:选择、限制、答案。
1. 选择。基本就是根据题意模拟。
2. 限制。如果题目中有多种限制,我们可以枚举一种,检验是否满足其他限制。有时,答案也可以看作一种限制。
3. 答案。检查答案是否满足约束;如果答案具有单调性,可以二分。
TEXT Greedy Algorithm
Barn Repair [1999 USACO Spring Open]
有一些畜栏排成一列,有的需要覆盖木板。木板的尺寸不限,给定木板数量N,要求最小化覆盖的畜栏总量。
一种角度:从N到(N+1)
这是原文的角度。假设我们已经得到了(N-1)块木板的最优解,怎样扩展到N块木板?这个有点类似于动态规划。一种尝试是,我们要移走覆盖着最长非必要区间的那块板,换成两块,一个在那个区间的左边,一个在那个区间的右边。
我觉得原文的正确性证明不对。这种扩展方式是显然的,关键要证明最优子结构,即N的最优解包含(N-1)的最优解。我的水平还不够把这件事说清,但是,如果采取下面这种角度,一切都很显然。
另一种角度:反过来看
找到第一个必须被覆盖的畜栏和最后一个必须被覆盖的畜栏,问题等价于去掉(N-1)个非必须的区间,区间长度和尽可能大。因为每去掉一个区间,木板的总量增加1。这些区间是互不干扰的,于是取前(N-1)长的区间就好了。
Sorting a three-valued sequence [IOI 1996]
有一个序列,每个元素的取值范围是{1, 2, 3},求通过交换给它排序的最少步数。
算法:计算出1、2、3在有序情况下应该处于的区间。先还原一对对正好错位的元素,再还原所有还没归位的1,最后还原所有未归位的2。
同样不认可那个证明……但是自己也不会。
Friendly Coins - A Counter example [abridged]
探究什么条件下用贪心的策略换零钱是正确的是一个有趣的问题,不知道是否已经解决了。
Topological Sort
通过检查顶点的入/出度,更新,来拓扑排序。这是贪心吗?
PROB Mixing Milk
给定需要的牛奶量,每个农民可供应的牛奶量、每单位牛奶的价格,求满足需要的采购方案的最小代价。
排个序就好啦。ANALYSIS页面好多题解,其实就是两类:用快排的和用计数排序的。我用的是STL sort……
/*
ID: chrt2001
PROG: milk
LANG: C++
*/
#include <cstdio>
#include <algorithm>
using namespace std;
struct Farmer {
int p, a;
bool operator<(const Farmer& rhs) const
{
return p < rhs.p;
}
} F[5000];
int main()
{
freopen("milk.in", "r", stdin);
freopen("milk.out", "w", stdout);
int n, m, ans = 0;
scanf("%d %d", &n, &m);
for (int i = 0; i < m; ++i)
scanf("%d %d", &F[i].p, &F[i].a);
sort(F, F+m);
for (int i = 0; n > 0; ++i) {
int buy = min(n, F[i].a);
ans += buy * F[i].p;
n -= buy;
}
printf("%d\n", ans);
return 0;
}
PROB Barn Repair
分析见前文。
这次我用了计数排序。
/*
ID: chrt2001
PROG: barn1
LANG: C++
*/
#include <cstdio>
#include <algorithm>
using namespace std;
bool O[201];
int S[199];
int main()
{
freopen("barn1.in", "r", stdin);
freopen("barn1.out", "w", stdout);
int m, s, c, mn =