算法学习基础篇(二):贪心Ⅱ

参考书籍《挑战程序设计》,本文实质为该书的学习笔记,结合了笔者自己的理解,欢迎指错~

本篇为算法学习基础篇(二):贪心Ⅰ的加强版

新手小伙伴们可以从↑看起


以下内容摘抄自百度百科

贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。


接下来通过具体例子来熟悉和了解贪心算法

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)。

O(NlogN)的求解方法


代码如下:

#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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值