参考书籍《挑战程序设计》,本文实质为该书的学习笔记,结合了笔者自己的理解,欢迎指错~
本篇为算法学习基础篇(二):贪心Ⅰ的加强版
新手小伙伴们可以从↑看起
以下内容摘抄自百度百科
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
接下来通过具体例子来熟悉和了解贪心算法
1.Saruman’s Army(POJ 3069)
直线上有n(1≤n≤1000)个点,点i的位置是Xi(1≤n≤1000)。从这n个点中选择若干个,给它们加上标记。对每一个点,其距离为R(1≤n≤1000)以内的区域里必须有带有标记的点。(自己本身带有标记的点,可以认为与其距离为0的地方有一个带有标记的点)在满足这个条件的情况下,希望给尽可能少的点添加标记。请问至少要有多少点被加上标记?
(PS:这里的题目和原题有出入)
输入:
6
10
1 7 15 20 30 50
输出:
30
思路:从最左边的点开始,距离为R以内的最远的点加上第一个标记。从第一个标记的点右侧R距离外的第一个点开始,距离为R以内的最远的点加上第二个标记。依次,直到覆盖所有点为止。
代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAX_N = 1000;
int n, R;
int X[MAX_N];
int main()
{
scanf("%d%d", &n, &R);
for(int i = 0; i < n; i++)
scanf("%d", &X[i]);
sort(X, X + n);
int t = X[0], s = t + R, cnt = 0;
int i = 1;
while(s <= X[n - 1])
{
if(s < X[i])
t = X[i + 1];
else
{
while(s >= X[i])
i++;
t = X[i];
}
s = t + R;
cnt++;
}
printf("%d\n", cnt);
return 0;
}
我的代码貌似不好理解,附上作者的代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAX_N = 1000;
int n, R;
int X[MAX_N];
int main()
{
scanf("%d%d", &n, &R);
for(int i = 0; i < n; i++)
scanf("%d", &X[i]);
sort(X, X + n);
int i = 0, ans = 0;
while(i < n)
{
//s是没有被覆盖的最左的点的位置
int s = X[i--];
//一直向右前进直到距s的距离大于R的点
while(i < n && X[i] <= s + R)
i++;
//p是新加上标记的点的位置
int p = X[i - 1];
//一直向右前进直到距p的距离大于R的点
while(i < n && X[i] <= p + R)
i++;
ans++;
}
printf("%d\n", ans);
return 0;
}
2.Fence Repair(POJ 3253)
农夫约翰为了修理栅栏,要将一块很长的模板切割成n(1≤n≤20000)块。准备切成的模板的长度为L1、L2、... 、Ln(0≤Li≤50000),未切割前木板的长度恰好为切割后木板长度的总和。例如长度为21的木板要切成长度为5、8、8的三块木板。长21的木板切成13、8的板时,开销是21。再将长度为13的板切成长度为5、8的板时,开销是13。于是合计开销为34。请求出按照目标要求将木板切割完最小的开销是多少。
输入:
3
8 5 8
输出:
34
回来补坑来了
思路:
公式:木板的长度×节点的深度
(忽略我小学生的字罢0.0)
最短的板与次短的板应当是兄弟节点,且最短的板应当是深度最大的叶子结点之一。可将Li按大小顺序排列,则最短的板为L1,次短的板为L2。它们在二叉树中是兄弟结点,也就是它们是从一块长度为(L1+L2)的板切割而来的。可将它们看作是最后一次切割而来的,在这次切割之前,有(L1+L2),L3,L4,…,Ln这样N-1块木板存在。可使用递归求解,复杂度为O(N^2)。
代码如下:
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int MAX_N = 20005;
int N, L[MAX_N];
int main()
{
scanf("%d", &N);
for(int i = 0; i < N; i++)
scanf("%d", &L[i]);
ll ans = 0;
//计算到木板为1块时终止
while(N > 1)
{
//求出最短的板mii1和次短的板mii2
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);
return 0;
}