贪心算法的应用实例:子问题的局部最优解可推导出全局最优解

下面,我将用三道题目说明贪心算法的思维方式、使用范围、使用方法、排序不等式证明和过程的推导。

一、多操作的贪心算法实现:如何趋向最优解

1. 形式化题目描述

现有两个数 X X X Y Y Y ,要求我们用以下两种操作在最少的次数内将 X X X 变成 Y Y Y

  • X ← 2 X X \leftarrow 2X X2X
  • X ← X − 1 X \leftarrow X-1 XX1
    求最少操作次数。
    数据范围: 1 ≤ X , Y ≤ 1 × 1 0 10 1 \le X , Y \le 1 \times 10 ^{10} 1X,Y1×1010
    在这里插入图片描述

2. 实现方式

算法的选择:我们首先考虑使用 DFS \texttt{DFS} DFS 算法。对于每个节点,分两种情况:乘以 2 2 2 ,减去 1 1 1 。如果我们发现目前的数已经大于 2 × 1 0 10 2 \times 10 ^ {10} 2×1010 ,则到达递归边界,退出该轮递归。否则,继续分支,直到 Y = X Y=X Y=X 为止,并记录当前的操作次数。如果当前的操作次数是最优操作次数,更新答案,重新寻找是否有更优的操作次数。但是,这种算法的时间复杂度为 O ( 2 Y − X ) O(2^{Y-X}) O(2YX) ,所以严重超时。
如果我们使用 DP \texttt{DP} DP ,则我们需要定义一个规模为 2 × 1 0 10 2 \times 10^{10} 2×1010 的数组,并且需要循环两重,所以时间复杂度最高可以达到 4 × 1 0 20 4 \times 10^{20} 4×1020 ,依旧严重超时。因此,我们考虑使用复杂度更低的贪心算法。

结论:我们可以使用逆向的推理方法,当 Y Y Y 能除以 2 2 2 时(定义: Y > X Y>X Y>X Y mod  2 = 0 Y \text{mod } 2 = 0 Ymod 2=0)将 Y ← Y 2 Y \leftarrow \large{\frac{Y}{2}} Y2Y ,否则将 Y ← Y + 1 Y \leftarrow Y+1 YY+1 ,直到 X = Y X=Y X=Y 为止。
证明:使用反证法求证。
如果我们在 Y Y Y 能除以 2 2 2 时不除以 2 2 2 而选择加上 1 1 1 Y > X Y>X Y>X,则
∵ \because 1 ≤ Y ≤ 1 × 1 0 10 1 \le Y \le 1 \times 10^{10} 1Y1×1010 ,即 Y ∈ N * Y \in \mathbb{N}\text{*} YN*
∴ \therefore Y + 1 > Y 2 Y+1>\large{\frac{Y}{2}} Y+1>2Y
∵ \because Y > X Y>X Y>X
∴ \therefore Y + 1 − X > Y 2 − X Y+1-X>\large{\frac{Y}{2}}-X Y+1X>2YX
∴ \therefore 无论我们如何变换 Y Y Y X X X Y 2 \large{\frac{Y}{2}} 2Y 始终更加接近正确答案。
∴ \therefore Y ← Y 2 Y \leftarrow \large{\frac{Y}{2}} Y2Y 能花费更少的次数。
同理,如果我们在 Y ≤ X Y \le X YX 时选择将 Y ← Y 2 Y \leftarrow \large{\frac{Y}{2}} Y2Y ,则
∵ \because X − ( Y + 1 ) > X − Y 2 X-(Y+1)>X-\large{\frac{Y}{2}} X(Y+1)>X2Y (部分与上面类似的条件省略),
∴ \therefore Y ← Y + 1 Y \leftarrow Y+1 YY+1 能花费更少的次数。
证毕。

误区:误认为使用顺向思维,将 X X X 不断乘以 2 2 2 ,最后可以使次数最少。
反例如下:让 X = 1 , Y = 1 × 1 0 10 X=1,Y=1 \times 10^{10} X=1,Y=1×1010 ,则使用上述的贪心算法只需要花费 39 39 39 次,但使用上面的错误算法将要花费
       ⌈ log ⁡ 2 Y ⌉ + 2 ⌈ log ⁡ 2 Y ⌉ − Y = ⌈ log ⁡ 2 1 × 1 0 10 ⌉ + 2 ⌈ log ⁡ 2 Y ⌉ − Y = 34 + 2 34 − 1 0 10 = 34 + 7179869184 = 7179869218 > 39 \begin{equation} \begin{split} \tag*{}&\text{ \text{ }\text{ }\text{ }\text{ }\text{ }}\lceil \log_2{Y} \rceil +2^{\lceil \log_2{Y}\rceil }-Y \\ &=\lceil\log_2{1 \times 10^{10}}\rceil + 2^{\lceil\log_2{Y}\rceil}-Y \\ &=34+2^{34}-10^{10} \\ &=34+7179869184 \\ &=7179869218\\ &> 39 \end{split} \end{equation}       log2Y+2log2YY=log21×1010+2log2YY=34+2341010=34+7179869184=7179869218>39
∴ \therefore 不能使用以上方法求解。

时间复杂度 O ( log ⁡ 2 Y log ⁡ 2 X ) O(\large{\frac{\log_2{Y}}{\log_2{X}}}) O(log2Xlog2Y)

3. 代码实现

#include <bits/stdc++.h>

using namespace std;

#define ONLINE_JUDGE 1

typedef long long ll;

ll x, y;
int ans;

int main()
{
#if ONLINE_JUDGE
    freopen("change.in", "r", stdin);
    freopen("change.out", "w", stdout);
#endif
    
    cin >> x >> y;

    while (y > x)
    {
        if (y % 2 == 0) y /= 2;
        else y++;

        ans++;
    }
    ans += x - y;

    cout << ans << endl;

    return 0;
}

二、“排队接水”变形题目的贪心算法实例:分析排序不等式

1. 形式化题目描述

我们现在有两个长度均为 N N N 的序列 a a a b b b ,试通过恰当的位置调换使 ∑ i = 1 N ( a i ∑ j = 1 i b j ) \sum^{N}_{i=1} (a_i\sum^{i}_{j=1}b_j) i=1N(aij=1ibj) 最小。
在这里插入图片描述

2. 实现方式

算法的选择:此题可以看作带权值的“排队接水”问题,故应该使用贪心算法分析排序的不等式。

结论:排序不等式为 a j b i < a i b j a_jb_i<a_ib_j ajbi<aibj

证明:假设我们当前看到的是第 i i i 个和第 j j j 个。
∵ \because 当前两项的和有两种可能: a i b i + a j ( b i + b j ) a_ib_i+a_j(b_i+b_j) aibi+aj(bi+bj) a j b j + a i ( b i + b j ) a_jb_j+a_i(b_i+b_j) ajbj+ai(bi+bj)
∴ \therefore 排序不等式为 a i b i + a j ( b i + b j ) < a j b j + a i ( b i + b j ) a_ib_i+a_j(b_i+b_j)<a_jb_j+a_i(b_i+b_j) aibi+aj(bi+bj)<ajbj+ai(bi+bj)
∴ \therefore 经过推导可得
a i b i + a j b i + a j b j < a j b j + a i b i + a i b j a i b i + a j b i + a j b j < a j b j + a i b i + a i b j a j b i < a i b j \begin{align} \tag*{}a_ib_i+a_jb_i+a_jb_j&<a_jb_j+a_ib_i+a_ib_j \\ \tag*{}\bcancel{a_ib_i}+a_jb_i+\bcancel{a_jb_j}&<\bcancel{a_jb_j}+\bcancel{a_ib_i}+a_ib_j \\ \tag{1}a_jb_i&<a_ib_j \end{align} aibi+ajbi+ajbjaibi +ajbi+ajbj ajbi<ajbj+aibi+aibj<ajbj +aibi +aibj<aibj(1)
不等式 ( 1 ) (1) (1) 即为所求。
证毕。

时间复杂度:瓶颈为快排的复杂度, O ( N log ⁡ 2 N ) O(N\log_2{N}) O(Nlog2N)

3. 代码实现

#include <bits/stdc++.h>

using namespace std;

#define ONLINE_JUDGE 1

const int N = 1e5 + 10;

struct Node
{
    long long x, y;
} a[N];

long long n, ans;

int main()
{
#if ONLINE_JUDGE
    freopen("eat.in", "r", stdin);
    freopen("eat.out", "w", stdout);
#endif

    cin >> n;
    for (int i = 1; i <= n; ++i)
    {
        cin >> a[i].x >> a[i].y;
        //x重要度 y等待时间
    }

    sort(a + 1, a + n + 1, [&](const Node& a, const Node& b)
    {
        return a.y * b.x < a.x * b.y;
    });
    int t = 0;
    for (int i = 1; i <= n; ++i)
    {
        t += a[i].y;
        ans += t * a[i].x;
    }

    cout << ans << endl;

    return 0;
}

三、多任务执行测试的排序不等式推理方法

1. 题意简述

我们现在要在一台电脑上运行 N N N 个程序,每个程序在运行时需要花费 r i r_i ri 的内存空间,最小化进入后台后需要花费 o i o_i oi 的内存空间。
已知我们要运行全部的程序,请问最少需要占用多少的内存空间?

2. 算法框架

本题的关键在于排序不等式的推理。

我们首先关注题意,题目让我们求的是最少需要占用的内存空间数。那么,我们可以从以下几个方面来思考解决方式:
首先,我们发现,每次运行占用空间最多的项就是当我们存储上面的第 i i i 个程序并开始运行下面的第 j j j 个程序时或只是上面的第 i i i 个程序运行时占用最大的内存空间。那么,我们可以得到排序不等式如下:
m a x { r i , r j + o i } < m a x { r j , r i + o j } max\{r_i,r_j+o_i\} < max\{r_j,r_i+o_j\} max{ri,rj+oi}<max{rj,ri+oj}

总之,该题的整体思路与上面的题类似。

3. 代码实现

#include <bits/stdc++.h>

using namespace std;

#define ONLINE_JUDGE 1

const int N = 1e5 + 10;

struct Node
{
    int o, r;
} a[N];

int n;

int main()
{
#if ONLINE_JUDGE
    freopen("task.in", "r", stdin);
    freopen("task.out", "w", stdout);
#endif

    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    cin >> n;
    for (int i = 1; i <= n; ++i)
    {
        cin >> a[i].r >> a[i].o;
    }

    sort(a + 1, a + n + 1, [&](const Node& a, const Node& b)
    {
        return max(a.r, b.r + a.o) < max(b.r, a.r + b.o);
    });
    int ans = 0, t = 0;
    for (int i = 1; i <= n; ++i)
    {
        ans = max(ans, t + a[i].r);
        t += a[i].o;
    }

    cout << ans << endl;

    return 0;
}

四、总结

在使用贪心算法的时候,不要尝试去证明一个贪心算法,这样会花费大量的时间。我们可以尝试举出反例,反例一般都是在特殊或极端数据下出现。在不确定是否应该使用一个贪心算法的时候,我们可以使用对拍等方法来验证。若这种题考查的是排序不等式,则我们可以使用数学方法来推导这个不等式。否则,我们应该用画图理解的方式尝试去说明一个贪心策略的合理性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值