首页 > 编程语言 >浅谈贪心算法

浅谈贪心算法

时间:2024-11-12 21:45:36浏览次数:1  
标签:浅谈 max sum pos 算法 区间 线段 贪心

浅谈贪心算法

贪心算法,指在问题求解时,每一步都做出“当前看起来最好的决策”。它没有固定的算法模板,灵活性强。在 OI 领域,无论是入门组,还是省选,NOI,或多或少都出过贪心题。可见贪心的重要性之大。

使用贪心算法解决问题,必须满足 “无后效性”。满足 “无后效性” 不一定当前的决策对后续选择绝对无影响,只要满足当前决策不会影响后续最优解的选择即可。反之,动态规划问题必须满足当前决策对后续决策绝对无影响。在动态规划问题中,我们开多层状态,主要目的在于满足无后效性。

诚然,贪心策略的选择是重要的,但结论的证明也不可忽视。下文将给出部分贪心常用证明方法。

证明

邻项交换法

此类题目往往需确定一个决策顺序,当交换两者决策顺序,不影响其他决策时,可使用邻项交换法确定决策顺序。

例题精讲

共有 \(n\) 个物品,每个物品有两个值,分别为 \(w_i,s_i\)。
你可以对这 \(n\) 个物品任意排序。每个物品的贡献 \(W_i=\sum\limits_{j=i+1}^n w_j-s_i\),最小化 $\max{W_i} $ 。
\(1\le n \le 5\times 10^4\)
Source

一看数据范围,排序题。大概率是按照某种策略进行排序然后模拟。

难点在于排序策略。当然多重贪心加上随机化有可能获得可观的分数。

我们用邻项交换的方式思考本题。因为不难发现,交换 \(i,i+1\) 物品后,不会对其他物品产生影响。

从上到下的物品贡献可以表示为 \(\sum\limits_{k=1}^{i-1}w_k-s_i\)。

下文记 \(sum=\sum\limits_{k=1}^{i-1}w_k\)。

交换前,两个物品的贡献分别为 \(sum-s_i,sum+w_i-s_{i+1}\)。

交换后则分别为 \(sum-s_{i+1},sum+w_{i+1}-s_i\)。

因此,当满足

\[\max\{sum-s_{i+1},sum+w_{i+1}-s_{i}\}<\max\{sum-s_{i},sum+w_{i}-s_{i+1}\} \]

交换可能会使答案更优。

下面的操作基于以下原则,需要记住。

\(\max(a,b)<c \Leftrightarrow a<c\text{且}b<c\)

\(\max(a,b)>c \Leftrightarrow a>c\text{或}b>c\)

\(\max(a+b,a+c)=a+\max(b,c)\)

因此,上式可以消去 \(sum\),得

\[sum+\max(-s_{i+1},w_{i+1}-s_{i})<sum+\max(-s_{i},w_{i}-s_{i+1}) \]

也就得到了交换条件。

\[\max(-s_{i+1},w_{i+1}-s_{i})<\max(-s_{i},w_{i}-s_{i+1}) \]

依据 \(\max(a,b)>c \Leftrightarrow a>c\text{或}b>c\),我们将上式拆开。

\[-s_{i+1}<\max(-s_i,w_{i}-s_{i+1}) \]

\[w_{i+1}-s_{i}<\max(-s_i,w_{i}-s_{i+1}) \]

因为 \(s_i,w_i,s_{i+1},w_{i+1}\) 恒大于 \(0\),故下列条件一定满足。

\[-s_{i+1}<\max\{-s_i,w_i-s_{i+1}\} \]

\[-s_i<\max\{-s_{i+1},w_{i+1}-s_i\} \]

证明还是依据 \(\max(a,b)>c \Leftrightarrow a>c\text{或}b>c\)。这里不再赘述。

刚才提到,若交换条件

\[sum+\max(-s_{i+1},w_{i+1}-s_{i})<sum+\max(-s_{i},w_{i}-s_{i+1}) \]

成立,则一定有

\[-s_{i+1}<\max(-s_i,w_{i}-s_{i+1}) \]

\[w_{i+1}-s_{i}<\max(-s_i,w_{i}-s_{i+1}) \]

成立

前面已经证明,一式一定成立,我们只需要关心第二个式子。

还是把它拆开,显然 \(w_{i+1}-s_i<-s_i\) 是一定成立的,我们只需要关心 \(w_{i+1}-s_i<w_i-s_{i+1}\)。

移项,得。

\[w_{i+1}+s_{i+1}<w_i+s_i \]

证毕.

归纳法

数学归纳法

一般的,证明一个与正整数 \(n\) 有关的命题,可使用数学归纳法。具体步骤如下。

  • (归纳奠基)证明当 \(n=n_0(n_0\in\operatorname{\N_{+}})\) 时成立。
  • (归纳递推)以 “当 \(n=k(k\in \operatorname{\N_{+}},k\ge n_0)\) 时命题成立” 为条件,推出 “当 \(n=k+1\)” 时命题也成立。

由上述二步骤即可断定命题对 从 \(n_0\) 开始的所有正整数 \(n\) 都成立

正确性. 由皮亚诺公理中 “归纳公理“ 可得,若某个性质在 \(0\) 成立,并且对于任意自然数 \(n\),若该性质在 \(n\) 成立,则它在 \(S(n)\) 也成立,那么该性质对于所有自然数都成立。事实上,数学归纳法和 “归纳公理” 本质相同,这也就意味着,数学归纳法作为一个公理,正确性是毋庸置疑的。

我们亦可这样理解,皮亚诺公理规定,任何一个自然数 \(i\) 有且仅有一个后继 \(i'\),既然 \(P(i)\Rightarrow P(i')\),且证得 \(P(s)\) 成立,则从 \(P(s)\) 可推得任意在区间内的 \(x\),命题 \(P\) 都成立。

Claim. 对于任意的自然数 \(n\),都有 \(1+2+3+4+\dots +n=\dfrac{n(n+1)}{2}\).

Proof.

最小的自然数为 \(1\),显然当 \(n=1\) 时命题成立.

设当 \(n=k(k\in \operatorname{\N})\) 时,命题成立,即得 \(1+2+3+4+\dots k=\dfrac{k(k+1)}{2}\),下面证明当 \(n=k+1\) 时命题成立,即证 \(1+2+3+4+\dots k+1=\dfrac{(k+1)(k+2)}{2}\).

\[1+2+3+4+\dots +k+1=1+2+3+4+\dots +k+k+1=\dfrac{k(k+1)}{2}+k+1=\dfrac{k(k+1)+2(k+1)}{2}=\dfrac{(k+1)(k+2)}{2} \]

命题成立.

即对于任意的自然数 \(n\),都有 \(1+2+3+4+\dots +n=\dfrac{n(n+1)}{2}\).

应用归纳法证明经典贪心算法

Kruskal 最小生成树算法

算法流程. 将所有候选边按照边权升序排序,依次决策。若加入该边,图上无环,则加入。否则跳过。当加入 \(n-1\) 条边后决策结束。时间复杂度 \(O(m\log m)\),其中 \(m\) 为边数。

一般的,我们认为,Kruskal 的时间复杂度只与边数 \(m\) 有关。这是一个和自然数有关的命题,考虑使用数学归纳法证明之。

Proof.

  • (归纳奠基)当 \(m=1\) 时显然成立.

  • (归纳递推)设当 \(m=k\) 时,执行 Kruskal 算法所取的边集为 \(T'\),还未选择的,且加入 \(T'\) 后不会形成环的,边权最小的边为 \(e\)。当 \(m=k+1\) 时,最小生成树为 \(T\)。下面证明 \(T=T'+e\)。

    • 若 \(e\in T\),显然成立.
    • 若 \(e\notin T\),将 \(e\) 放入边集 \(T\) 中,会形成环。环上必定存在至少一条边 \(f\) 不在 \(T'\) 中,我们分析 \(f\) 和 \(e\) 的关系.
      • \(f<e\),执行 kruskal 算法时,\(f\) 应当在 \(e\) 之前被选。但 \(f\) 不应当在 \(T'\) 中(否则当前会形成环),矛盾.
      • \(f>e\),此时,我们通过舍弃 \(f\),使用 \(e\) 可获得比原本 MST 更小的生成树。矛盾.

    综上所述,\(f=e\)。命题 \(e\notin T\) 不成立.

综上,我们证明了执行 Kruskal 算法,每一步添加的边必定在 MST 中.

基于贪心思想的区间覆盖问题

区间完全覆盖问题

有 \(n\) 条线段,第 \(i\) 条线段覆盖区间 \([l,r]\),求至少需要多少条线段,覆盖区间 \([1,m]\)。

这是一个经典问题。我们采用贪心的策略解决,具体如下。

Greedy select. 令 \(pos\) 为当前已覆盖区间右端点,找到一条左端点 \(\le pos+1\),右端点最大的线段,并更新 \(pos\)。

Proof. 该策略每次选择的为合法的,右端点最大的线段。每次的选择能够将 \(pos\) 扩展到最大,即将已覆盖区间扩展到最大。

我们亦可从全局最优性证明。由于每次选择区间必须合法,显然,\(pos\) 越大,可选择的区间越多。故该贪心策略必定导致答案更优不劣。

Example.

namespace solution
{
    #define x first
    #define y second
    typedef pair<int,int> PII;
    vector <PII> line;
    int n,m;
    void solve()
    {
        cin>>n>>m;
        for(int i=1;i<=n;i++)
        {
            int l,r;
            cin>>l>>r;
            line.push_back(PII(l,r));
        }
        sort(line.begin(),line.end());
        int pos = 1,tot = 0,ans = 0;
        while(pos < m)
        {
            int maxn = 0;
            for(int i = tot;i < line.size();i++) 
            {
                if(line[i].x <= pos + 1) maxn = max(maxn,line[i].y);
                else 
                {
                    tot = i;
                    break;
                } 
            }
            pos = maxn;
            ans ++;
        }
        cout<<ans<<"\n";
        return;
    }   
}

给定 \(k\) 条线段,询问 \(q\) 次,每次询问最少用多少条线段覆盖区间 \([l,r]\)。

一个比较显然的策略是,对于每次询问,令 \(pos=l\) 执行上述 Greedy select。但复杂度并不理想。

Claim. 从 \(i\) 经线段 \([i,j]\) 跳到 \(j\) 后,执行 Greedy select 选择的线段和前面选择的必不重复。

Proof. 若从 \(j\) 贪心选择线段和前面重复,则前面可选择该线段以获得更大的 \(pos\)。矛盾。

因此,对于任意的 \(i\),执行 \(k\) 次 Greedy select独立 的,不受前面选择影响。据此,我们预处理 \(f_{i,j}\) 表示 \(i\) 执行 \(2^j\) 次 Greedy select 扩展到的 \(pos\)。对于每次询问,从 \(l\) 倍增跳即可。

Example.

for(int i=1,j=1,maxn = 0;i<=n*2;i++)
{
    while(j <= k && p[j].x <= i) 
    {
        r = max(r,p[j].y+1);
        j++; 
    }
    f[0][i] = r;
}
for(int i=1;i<=20;i++)
    for(int j=1;j<=n;j++) f[i][j] = f[i-1][f[i-1][j]];

给定一个长度为 \(n\) 的环,有 \(k\) 个区域被覆盖,求最少需要几条线段覆盖整个环。
Source:洛谷 P6902 [ICPC2014 WF] Surveillance

该问题和上面类似,只是将序列变为环。常见的处理思路是断环为链。具体的,将所有跨过 \(1\) 的线段 \([l,r]\) 视为 \([l,n+r]\)。一条线段 \([l,r]\) 跨过 \(1\) 当且仅当 \(l>r\)。

Example. paste

最大不相交区间数问题

数轴上有 \(n\) 个区间 \([a_i,b_i]\),选择尽可能多的区间,使得区间两两不相交。

Greedy select. 将所有区间按照右端点排序,若右端点相同,按照左端点排序。顺序选模拟即可。

Proof / Analysis. 考虑反证法。假设贪心算法执行完毕后,扔掉其选择的一段区间 \([u,v]\),可选择另外若干条新区间 \([a_1,b_1],[a_2,b_2]\dots\) 且不会对其他已选择区间造成影响。分类讨论新区间与 \([u,v]\) 的关系。

  • \([a_i,b_i]\subseteq [u,v]\),这种情形可推广为 \(b_i\le v\)。根据贪心算法,不可能。

  • \(b_i\ge v\) 前面提到,不可能存在 \(b_i\le v\)。故交换二者不可能更优。

证毕.

带需求约束的最小集合覆盖问题

在一条数轴上,给定若干区间 \([l_i,r_i]\),每个区间都有一个约束 \(k_i\),要求区间内 至少 有 \(k\) 个点被染色,求满足所有约束条件下,至少染色多少个点。
P1250 种树 P1986 元旦晚会 SP116 POI2015 KIN
CSP-S2024 超速检测

Greedy Select. 考虑将所有询问区间离线到数轴上。将所有区间按照右端点排序。若该区间已经满足条件,忽略。否则从右边开始,枚举没染色的点染色。我们一定尽可能希望给一个区间染色能影响下一个区间,故从右往左染色。实现时可使用树状数组加速。

Proof. 由于我们总是从每个区间的右端开始满足需求,对于每一个区间,我们在尽可能靠右的位置放置必要的点以满足它的需求。任何尝试减少点数的方法都会导致某些区间无法满足需求,因为去除任何点都会导致至少一个区间的覆盖需求不足。

Example.


void solve()
{
    n = read<int>(),m = read<int>();
    bit.clear();
    ask.clear();
    memset(tree,0,sizeof(tree));
    for(int i=1;i<=m;i++) 
    {
        int a,b,c;
        a = read<int>(),b = read<int>(),c = read<int>();
        a ++,b ++;
        ask.push_back({a,b,c});
    }
    sort(ask.begin(),ask.end());
    int res = 0;
    for(auto [a,b,c]:ask)
    {
        int tot = bit.query(b) - bit.query(a-1);
        if(tot >= c) continue;
        for(int i = b;i>=a;i--)
        {
            if(tot >= c) break;
            if(!tree[i]) 
            {
                tree[i] = 1;
                bit.update(i,1);
                tot ++,res ++;
            }
        }
    }
    print(res,'\n');
    return;
}

反悔贪心

标签:浅谈,max,sum,pos,算法,区间,线段,贪心
From: https://www.cnblogs.com/SXqwq/p/18542720

相关文章

  • 2024年最新优化算法:海市蜃楼算法(Fata Morgana Algorithm ,FATA)介绍
    海市蜃楼算法(FataMorganaAlgorithm,FATA)是2024年提出一种新型的群体智能优化算法,它的设计灵感来源于自然现象中的海市蜃楼形成过程。FATA算法通过模仿光线在不均匀介质中的传播方式,提出了两种核心策略——海市蜃楼光过滤原则(MLF)和光传播策略(LPS)——来优化搜索过程,增强算法......
  • 利用阿燑目算法训练平台实现智能任务:从数据集到算法部署的完整流程
    引言在当今的数字化时代,算法训练已成为实现智能化任务的关键环节。通过专业的算法训练平台,如阿燑目算法训练平台,用户可以轻松完成从数据准备到算法部署的整个流程,实现各种智能应用。本文将基于阿燑目算法训练平台的使用手册,详细介绍如何利用算法训练平台完成智能任务。一、创......
  • 算法训练平台的内心独白
    我是阿燑目算法训练平台,大家都说我很神秘,今天就要好好和大家絮叨絮叨到底是怎么个事儿!在数字时代,算法训练平台成为了小微科技工作者在日常工作中不可或缺的一部分。我在这里分享我的一些服务和经验,希望能给你带来一些启发。网址:https://hub.atm008.com/首先,我提供图像集自......
  • 我是阿燑目,算法训练得看我
    在人工智能的浪潮中,计算机视觉领域正经历着前所未有的变革。作为这场变革的先锋,我——阿燑目算法训练平台,应运而生,专为深度学习打造,致力于简化图像识别模型的整个生命周期,从训练到部署,我无所不包。记住我们的网址:https://hub.atm008.com/一站式解决方案,让复杂变简单企业......
  • C语言第九周课——经典算法
    目录一、冒泡法排序1.1原理1.2代码实现(以升序排序为例) 1.3逻辑 1.4分析二、二分法查找2.1原理2.2代码实现 2.3逻辑2.4算法效率分析三、素数判断3.1原理3.2代码实现3.3逻辑3.4分析一、冒泡法排序1.1原理冒泡排序(BubbleSort)是一种简单的排序算法。它重......
  • 代码随想录算法训练营第十一天|LeetCode150.逆波兰表达式求值、239.滑动窗口最大值、3
    前言打卡代码随想录算法训练营第49期第十一天 φ(゜▽゜*)♪首先十分推荐学算法的同学可以先了解一下代码随想录,可以在B站卡哥B站账号、代码随想录官方网站代码随想录了解,卡哥清晰易懂的算法教学让我直接果断关注,也十分有缘和第49期的训练营大家庭一起进步。今日题目在学......
  • 【24年新算法故障诊断】基于FVIM-DBN四向量优化深度置信网络的故障诊断(Matlab代码,评估
    本文采用四向量优化算法(FVIM,2024年新算法)优化深度置信网络DBN的超参数,形成FVIM-DBN故障诊断模型,以进一步提升其在数据分类任务中的性能。深度置信网络(DBN)是经典强大的深度神经网络,是一种具有多个隐藏层的前馈深度神经网络。它由若干堆叠的受限玻尔兹曼机(RestrictedBolt......
  • 排序算法 -堆排序
    文章目录1.堆排序(HeapSort)1.1简介1.2堆排序的步骤1.3堆排序C语言实现1.4时间复杂度1.5空间复杂度1.堆排序(HeapSort)1.1简介堆是一种特殊的完全二叉树,分为最大堆(MaxHeap)和最小堆(MinHeap)。在最大堆中,每个节点的值都大于或等于其子节点的值;在最小堆中,每个......
  • 【HAProxy05】企业级反向代理HAProxy调度算法之静态算法与动态算法
    HAProxy调度算法HAProxy通过固定参数balance指明对后端服务器的调度算法,该参数可以配置在listen或backend选项中。HAProxy的调度算法分为静态和动态调度算法,但是有些算法可以根据不同的参数实现静态和动态算法相互转换。官方文档:http://cbonte.github.io/haproxy-dcon......
  • 模拟鼠标真人移动轨迹算法-易语言
    一.简介鼠标轨迹算法是一种模拟人类鼠标操作的程序,它能够模拟出自然而真实的鼠标移动路径。鼠标轨迹算法的底层实现采用C/C++语言,原因在于C/C++提供了高性能的执行能力和直接访问操作系统底层资源的能力。鼠标轨迹算法具有以下优势:模拟人工轨迹:算法能够模拟出非贝塞尔曲线......