<蓝桥杯软件赛>零基础备赛20周--第11周--贪心

报名明年4月蓝桥杯软件赛的同学们,如果你是大一零基础,目前懵懂中,不知该怎么办,可以看看本博客系列:备赛20周合集
20周的完整安排请点击:20周计划
每周发1个博客,共20周。
在QQ群上答疑:

在这里插入图片描述

文章目录

  • 1. 贪心思想
  • 2. 经典贪心问题
    • 2.1 部分背包问题
    • 2.2 不相交区间问题(或称为区间调度问题、活动安排问题)
    • 2.3 区间合并问题
    • 2.4 区间覆盖问题
  • 3. 例题
    • 3.1 买二赠一
    • 3.2 购物
    • 3.3 管道
  • 4. 习题

第9周:贪心

备赛20周活动已经到第11周了,快到期末考试阶段了。最近做题的人很少了。

1. 贪心思想

  贪心是蓝桥杯省赛的必考知识点,每年都有。
  贪心(Greedy)是容易理解的算法思想:把整个问题分解成多个步骤,在每个步骤,都选取当前步骤的最优方案,直到所有步骤结束;在每一步,都不考虑对后续步骤的影响,在后续步骤中也不能回头改变前面的选择

作者拟过2句赠言:
   “贪心说,我从不后悔我走过的路”
   “贪心说,其实我有一点后悔,但是我回不了头”
大多数读者选前一句。

  贪心策略在生活中经常用到。例如下象棋时,初级水平的棋手只会“走一步看一步”,就是贪心法。而水平高的棋手能“走一步看三步”,轻松击败初级棋手。
  贪心这种“只顾当下,不管未来”的解题策略,让人疑惑:在完成所有局部最优操作后,得到的解不一定是全局最优,那么热如何判断能不能用贪心呢?
  有时很容易判断:一步一步在局部选择最优,最后结束时能达到全局最优。例如吃自助餐,怎么吃才能“吃回票价”?它的数学模型是一类背包问题,称为“部分背包问题”:有一个容量为C的背包,有m种物品,第i种物品有wi千克,单价为vi,且每种物品是可以分割的,例如大米、面粉等;问如何选择物品,使得装满背包时,总价值最大。显然可以用贪心法,只要在当前物品中选最贵的放进背包就行了:先选最贵的物品A,A放完之后,再选剩下最贵的物品B,…,直到背包放满。
  有时看起来能用贪心,但实际上贪心的结果不是最优解。例如最少硬币支付问题:有多种面值的硬币,数量不限;需要支付M元,问怎么支付,才能使硬币数量最少?
  最少硬币支付问题是否能用贪心求最优解,和硬币的面值有关。
  任意面值的最少硬币支付问题,正解是动态规划。参考《算法竞赛入门到进阶》清华大学出版社,罗勇军著,“7.1.1 硬币问题”给出了各种硬币问题的动态规划解法。
  如果硬币面值为1元、2元、5元,用贪心是对的。贪心策略是当前选择可用的最大面值的硬币。例如支付M=18元,第一步选面值最大的5元硬币,用掉3个硬币,还剩3元;第二步选面值第二大的2元硬币,用掉1个硬币,还剩1元;最后选面值最小的1元硬币,用掉1个;共用5个硬币。在这个解决方案中,硬币数量总数是最少的,贪心法的结果是全局最优的。
  但是如果是其他面值的硬币,贪心法就不一定能得到全局最优解。例如,硬币的面值很奇怪,分别是1、2、4、5、6元。支付M = 9元,如果用贪心法,每次选择当前最大面值硬币,那么答案是6 + 2 + 1,需要3个硬币,而最优解是5 + 4,只需要2个硬币。
  判断一个题目是不是能用贪心,需要满足以下特征:
  1)最优子结构性质。当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质,也称此问题满足最优性原理。
  2)贪心选择性质。问题的整体最优解可以通过一系列局部最优的选择来得到。也就是说,通过一步步局部最优能最终能得到全局最优。
  最后讨论贪心法的效率,贪心法的计算量是多少?贪心法由于每一步都在局部做计算,且只选取当前最优的步骤做计算,不管其他可能的计算方案,所以计算量很小。在很多情况下,贪心法可以说是计算复杂度最低的算法了。与此相对,暴力法一般是计算复杂度最差的,因为暴力法计算了全局的所有可能的方案。
  由于贪心的效率高,所以如果一个问题确定可用贪心法能得到最优解,那么应该使用贪心。如果用其他算法,大概率会超时。
  在算法竞赛中,贪心法几乎是必考点,有的题考验思维能力,有的题结合了贪心和其他算法。虽然贪心策略很容易理解,但贪心题可能很难。
  贪心也是蓝桥杯大赛的常见题型。不论是省赛还是国赛,贪心出现的概率都非常大。
  虽然贪心法不一定能得到最优解,但是它解题步骤简单、编程容易、计算量小,得到的解“虽然不是最好,但是还不错!”。像蓝桥杯这种赛制,一道题有多个测试点,用贪心也许能通过10%~30%,若别无他法,值得一试。

2. 经典贪心问题

2.1 部分背包问题

  前文介绍了用贪心求解部分背包问题,下面是例题。
例题:部分背包问题
  下面直接给出代码。
C++代码

#include<bits/stdc++.h>
using namespace std;
struct gold{ double w,v,p; }a[105];  //w,v,p: 重量,价值,单价
bool cmp(gold a, gold b){ return a.p > b.p;  } //单价从大到小排序
int main(){
    int n,c; cin>>n>>c;
    for(int i=0;i<n;i++){
        cin >> a[i].w >> a[i].v;
        a[i].p = a[i].v/a[i].w;  //计算单价
    }
    sort(a,a+n,cmp);         //按单价排序
    double sum=0.0;           //最大价值
    for(int i=0;i<n;i++){
        if(c >= a[i].w){     //第i种金币比背包容量小
            c -= a[i].w;     //背包还有余量
            sum += a[i].v;   //累计价值
        }
		else{               //第i种金币很多,直接放满背包
            sum += c*a[i].p;
            break;
        }
    }
    printf("%.2f",sum);//保留小数点后两位输出
	return 0;
}

Java代码

import java.util.*;
class Main {
    static class Gold { double w, v, p; }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int c = scanner.nextInt();
        Gold[] a = new Gold[n];
        for (int i = 0; i < n; i++) {
            a[i] = new Gold();
            a[i].w = scanner.nextDouble();
            a[i].v = scanner.nextDouble();
            a[i].p = a[i].v/a[i].w;
        }
        Arrays.sort(a, new Comparator<Gold>() {
            public int compare(Gold a, Gold b) {
                return Double.compare(b.p, a.p);
            }
        });
        double sum = 0.0;
        for (int i = 0; i < n; i++) {
            if (c >= a[i].w) {
                c -= a[i].w;
                sum += a[i].v;
            } else {
                sum += c * a[i].p;
                break;
            }
        }
        System.out.printf("%.2f", sum);
    }
}

Python代码

n, c = map(int, input().split())
a = []
for i in range(n):
    w, v = map(int, input().split())
    p = v / w
    a.append((w, v, p))
a.sort(key=lambda x: x[2], reverse=True)
sum = 0.0
for i in range(n):
    if c >= a[i][0]:
        c -= a[i][0]
        sum += a[i][1]
    else:
        sum += c * a[i][2]
        break
print("%.2f" %sum)

2.2 不相交区间问题(或称为区间调度问题、活动安排问题)

  给定一些区间(活动),每个区间有左端点和右端点(开始时间和终止时间),要求找到最多的不相交区间(活动)。
  以下按“活动安排问题”来解释。
  这个问题的目的是求最多活动数量,所以那种持续时间长的活动不受欢迎,受欢迎的是尽快结束的、持续时间短的活动。
  考虑以下3种贪心策略:
  1)按最早开始时间贪心:先选最早开始的活动a,当a结束后,再选下一个最早开始的活动。这种策略不好,因为它没有考虑活动的持续时间。假如a一直不结束,那么其他活动就不能开始。
  2)最早结束时间:先选最早结束的活动a,a结束后,再选下一个最早结束的活动。这种策略是合理的。越早结束的活动,越能腾出后续时间容纳更多的活动。
  3)用时最少:先选时间最短的活动a,再选不冲突的下一个最短活动。这个策略似乎也可行,但是很容易找到反例,证明这个策略不正确。
  下图的例子,
  用“策略1)最早开始时间”,选3;
  用“策略2)最早结束时间”,选1、2、5、6;
  用“策略3)用时最少”,选4、1、2。
  策略2)的结果是最好的。
在这里插入图片描述
  总结活动安排问题的贪心策略:先按活动的结束时间(区间右端点)排序,然后每次选结束最早的活动,并保证选择的活动不重叠。

例题:线段覆盖

C++代码

#include<bits/stdc++.h>
using namespace std;
struct data{
    int L, R;                             //开始时间、结束时间
}a[1000005];
bool cmp(data x,data y){return x.R<y.R; } //按照结束时间排序
int main(){
    int n;   cin >> n;
    for(int i=0;i<n;i++)   cin>>a[i].L>>a[i].R;
    sort(a,a+n,cmp);
    int ans = 0;
    int lastend = -1;
    for(int i=0;i<n;i++)
        if(a[i].L>=lastend) {
            ans++;
            lastend = a[i].R;
        }
    cout<<ans;
    return 0;
}

Java代码

import java.util.*;
class Main {
    static class Data { int L, R; }        // 开始时间、结束时间
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        Data[] a = new Data[n];
        for (int i = 0; i < n; i++) {
            a[i] = new Data();
            a[i].L = scanner.nextInt();
            a[i].R = scanner.nextInt();
        }
        Arrays.sort(a, new Comparator<Data>() {
            public int compare(Data x, Data y) {
                return x.R - y.R;
            }
        });
        int ans = 0;
        int lastend = -1;
        for (int i = 0; i < n; i++) 
            if (a[i].L >= lastend) {
                ans++;
                lastend = a[i].R;
            }
        
        System.out.println(ans);
    }
}

python代码

n = int(input())
a = []
for _ in range(n):
    L, R = map(int, input().split())
    a.append((L, R))
a.sort(key=lambda x: x[1])      # 按照结束时间排序。请与下一个例题比较
ans = 0
lastend = -1
for i in range(n):
    if a[i][0] >= lastend:
        ans += 1
        lastend = a[i][1]
print(ans)

2.3 区间合并问题

  区间合并问题:给定若干个区间,合并所有重叠的区间,并返回不重叠的区间个数。
  以下图为例,1、2、3、5合并,4、6合并,新区间是1’、4’。
在这里插入图片描述
  贪心策略:按区间左端点排序,然后逐一枚举每个区间,合并相交的区间。
  定义不重叠的区间个数(答案)为ans。设当前正在合并的区间的最右端点为end,枚举到第i个区间[Li, Ri]时:
  若Li≤end,说明与第i区间相交,需要合并,ans不变,更新end = max(end, Ri)。
  若Li > end,说明与第i区间不相交,ans加1,更新 end = max(end, Ri)。
  请读者用上图的例子,模拟合并过程。

2.4 区间覆盖问题

  区间覆盖问题:给定一个目标大区间,和一些小区间,问最少选择多少小区间,可以覆盖大区间。
  贪心策略:尽量找出右端点更远的小区间。
  操作步骤:先对小区间的左端点排序,然后依次枚举每个小区间,在所有能覆盖当前目标区间右端点的区间之中,选择右端点最大的区间。
  下图中,求最少用几个小区间能覆盖整个区间。先按左端点排序。设当前覆盖到了位置R,选择的小区间数量为cnt。
在这里插入图片描述
  从区间1开始,R的值是区间1的右端点A,R=A。cnt=1。
  找到能覆盖R=A的区间2、3,在区间2、3中选右端点更远的3,更新R为区间3的右端点B,R=B。cnt=2。
  区间4不能覆盖R=B,跳过。
  找到能覆盖R=B区间5,更新R=C。cnt=3。结束。

例题:区间覆盖

C++代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct data{
	ll L,R;
} a[100005];
bool cmp(data x,data y){return x.L < y.L; } //按左端点排序
int main(){
	int n; cin>>n;
	for (int i=0;i<n;i++) cin>>a[i].L>>a[i].R;
	sort(a,a+n,cmp);
	ll lastend=-1,ans=0;
	for (int i=0;i<n;i++)
		if (a[i].R >= lastend){
            ans += a[i].R - max(lastend,a[i].L)+1;
            lastend = a[i].R+1;
		}
	cout<<ans;
	return 0;
}

Java代码

import java.util.*;

class Main {
    static class Data {long L, R;}
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        Data[] a = new Data[n];
        for (int i = 0; i < n; i++) {
            a[i] = new Data();
            a[i].L = scanner.nextLong();
            a[i].R = scanner.nextLong();
        }
        Arrays.sort(a, new Comparator<Data>() {
            public int compare(Data x, Data y) {
                return Long.compare(x.L, y.L);
            }
        });
        long lastend = -1;
        long ans = 0;
        for (int i = 0; i < n; i++) 
            if (a[i].R >= lastend) {
                ans += a[i].R - Math.max(lastend, a[i].L) + 1;
                lastend = a[i].R + 1;
            }
        System.out.println(ans);
    }
}

python代码

class Data:          
    def __init__(self, L, R):
        self.L = L
        self.R = R
def cmp(x, y):   return x.L < y.L    #这里用函数比较。请对比上一题代码

n = int(input())
a = []
for _ in range(n):
    L, R = map(int, input().split())
    a.append(Data(L, R))
a.sort(key=lambda x: x.L)
lastend = -1
ans = 0
for i in range(n):
    if a[i].R >= lastend:
        ans += a[i].R - max(lastend, a[i].L) + 1
        lastend = a[i].R + 1
print(ans)

3. 例题

3.1 买二赠一

2023年蓝桥杯省赛 Java B组 G题 20分
看起来这20分不难拿哦

链接:买二赠一
  最贵的商品显然不能免单,买了2个不能免单的最贵商品后,获得一个免单机会,那么这个免单机会给谁呢?就给能免单的最贵的那个商品。这个贪心思路显然是对的。
  以样例为例,先排序得{8 7 5 4 2 1 1}。先购买最贵的8、7,然后可以免单的最贵的是2。再购买剩下的最贵的5、4,免单1。最后单独买1。总价是25。
  C++代码。需要查找价格为P/2的商品, 由于价格已经排序,可以用二分法加快查找的时间。这里直接用二分法的库函数lower_bound()查找。

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int a[N];
bool vis[N];  //vis[i]=1表示已经免单了
int main(){
    int n; scanf("%d", &n);
    for(int i = 0; i < n; i++)   scanf("%d", &a[i]);
    sort(a, a + n);
    long long ans = 0;
    int cnt = 0;
    int last = -1;   //购买的2件中的便宜的那件
    last_id = n-1;   //能免单的位置
    for(int i = n-1; i >= 0; i--){
        if(!vis[i])
            cnt++, ans += a[i], last = a[i];  //last是买的第2件
        if(cnt == 2){  //买了2个
            cnt = 0;
            int x = lower_bound(a , a  + last_id, last / 2) - a;  //找能免单的商品a[x]
            if(x > last_id || a[x] > last / 2)  x--;   //向下取整
            if(x>=0){
                vis[x] = 1;   //x免单了
                last_id = x-1; //后面能免单的区间范围是[0,last_id]
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}

Java代码。Java没有自带的二分函数,只好自己写一个lowerBound()。

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] a = new int[n];
        for (int i = 0; i < n; i++)       a[i] = scanner.nextInt();
        Arrays.sort(a);
        long ans = 0;
        int cnt = 0;
        int last = -1;
        int last_id = n - 1;
        boolean[] vis = new boolean[n];
        for (int i = n - 1; i >= 0; i--) {
            if (!vis[i]) {
                cnt++;
                ans += a[i];
                last = a[i];
            }
            if (cnt == 2) {
                cnt = 0;
                int x = lowerBound(a, 0, last_id, last / 2);
                if (x > last_id || a[x] > last / 2) x--;
                if (x >= 0) {
                    vis[x] = true;
                    last_id = x - 1;
                }
            }
        }
        System.out.println(ans);
    }
    private static int lowerBound(int[] a, int L, int R, int target) {
        while (L < R) {
            int mid = L + (R - L) / 2;
            if (a[mid] >= target)  R = mid;
            else                   L = mid + 1;
        }
        return L;
    }
}

Python代码。自带的二分函数是bisect_left()。

import bisect
n = int(input())
a = list(map(int, input().split()))
a.sort()
ans = 0
cnt = 0
last = -1
last_id = n - 1
vis = [False] * n
for i in range(n - 1, -1, -1):
    if not vis[i]:
        cnt += 1
        ans += a[i]
        last = a[i]
    if cnt == 2:
        cnt = 0
        x = bisect.bisect_left(a, last // 2, 0, last_id)
        if x > last_id or a[x] > last // 2:  x -= 1
        if x >= 0:
            vis[x] = True
            last_id = x - 1
print(ans)

3.2 购物

链接:购物
  为方便处理,把硬币面值从小到大排序。
  无解是什么情况?如果没有面值1的硬币,组合不到1,无解。如果有面值1的硬币,那么所有的X都能满足,有解。所以,无解的充要条件是没有面值1的硬币。
  组合出1~X的任意值,需要的硬币多吗?学过二进制的人都知道,1、2、4、8、…、 2 n − 1 2^{n-1} 2n1这n个值,可以组合出1~ 2 n 2^n 2n-1的所有数。这说明,只需要很少的硬币,就能组合出很大的X。
  设已经组合出1~s的面值,即已经得到数字1、2、3、…、s,下一步扩展到s+1。当然,如果能顺便扩展到s+2、s+3、…扩展得越大越好,这样就能用尽量少的硬币扩展出更大的面值。
  如何扩展到s+1?就是在数字1、2、3、…、s的基础上,添加一个面值为v的硬币,得到s+1。v可以选1、2、…、s+1,例如:v=1,s+1=s+v;v=2,s+1=s-1+v;…;v=s+1,s+1=v。如果v=s+2,就不能组合到s+1了。
  v的取值范围是[1, s+1],为了最大扩展,选v为[1, s+1]内的最大硬币,此时s扩展到s+v。这就是贪心策略。
  以本题的输入样例为例说明计算过程。设答案为ans。
  先选硬币1,得到s=1。ans=1。
  再选[1, s+1]=[1, 2]内的最大硬币2,扩展s=1为s=1+v=3。ans=2。
  再选[1, s+1]=[1, 4]内的最大硬币2,得到s=5。ans=3。
  再选[1, s+1]=[1, 6]内的最大硬币5,得到s=10。ans=4。
  再选[1, s+1]=[1, 11]内的最大硬币10,得到s=20。ans=5。此时s≥X,结束。
  所以仅需5个面值1、2、2、5、10的硬币,就可以组合得到1、2、3、4、…、20。
  C/C++代码。第11行找[1, s]内的最大面值硬币,可以用二分法优化。本题不优化也能通过测试。

#include <bits/stdc++.h>
using namespace std;
int a[105];         //存硬币面值
int main(){
    int x,n;  cin >> x >>n;
	for(int i=0;i<n;i++) cin >> a[i];
	sort(a,a+n);
	if(a[0]!=1){ cout<<-1; return 0;}   //无解
	int s=0,ans=0;
	while(s<x)
		for(int v=n-1;v>=0;v--)
            if(a[v]<=s+1) {  //找到[1,s]内的最大面值硬币a[v]
                s+=a[v]; //扩展s
                ans++;
                break;
            }
	cout << ans;
}

Java代码

import java.util.Arrays;
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int x = input.nextInt();
        int n = input.nextInt();
        int[] a = new int[n];
        for (int i = 0; i < n; i++)     a[i] = input.nextInt();        
        Arrays.sort(a);
        if (a[0] != 1) {  System.out.println(-1);  return; }
        int s = 0;
        int ans = 0;
        while (s < x) 
            for (int v = n - 1; v >= 0; v--) 
                if (a[v] <= s + 1) {
                    s += a[v];
                    ans++;
                    break;
                }
        System.out.println(ans);
    }
}

python代码

x, n = map(int, input().split())
a = list(map(int, input().split()))
a.sort()
if a[0] != 1:  print(-1); exit()
s, ans = 0, 0
while s < x:
    for v in range(n - 1, -1, -1):
        if a[v] <= s + 1:
            s += a[v]
            ans += 1
            break
print(ans)

3.3 管道

2023年蓝桥杯省赛 Python B组 D题 10分
这10分似乎不难拿

链接:管道
  按题目的设定,管道内是贯通的,每个阀门都连着一个进水管,打开阀门后会有水从这个进水管进入管道,并逐渐流到管道内所有地方。
  先解释样例。设长度L的单位是米,水流的速度是米/秒。
  L=1处的阀门在第S=1秒打开,T=5秒时,覆盖范围L-(T-S)=1-(5-1)=-3,L+(T-S)=1+(5-1)=5;
  L=6处的阀门在S=5秒打开,T=5秒时,只覆盖了L=6;
  L=10处的阀门在L=2秒打开,T=5秒时,覆盖范围L-(T-S)=10-(5-2)=7,L+(T-S)=10+(5-2)=13。
  所以这3个阀门在T=5时,覆盖了[-3, 5]、6、[7,13],管道所有传感器都检测到了水流。
  读者可能立刻想到可以用二分法猜时间T。先猜一个T,然后判断在T时刻是否整个管道有水。如何判断?位于Li的阀门,它影响到的小区间是[Li-(Ti-Si), Li+(Ti-Si)],n个阀门对应了n个小区间。那么问题转化为:给出n个小区间,是否能覆盖整个大区间,这就是上一节提到的“区间覆盖问题”
  本题还可以再简单一点。题目给的评测用例指出Li-1 < Li,即已经按左端点排序了,可以省去排序的步骤。
  C++代码。在check((t)函数中,定义last_L为当前覆盖到的最左端,last_R为最右端。然后逐个遍历所有的小区间,看它对扩展last_L、last_R有无贡献。所有小区间处理完毕后,如果[last_L、last_R]能覆盖整个[1, len]区间,这个时刻t就是可行的。
  第32行把二分mid写成mid = ((R - L) >> 1) + L而不是mid = (R + L) >> 1,是因为R+L可能溢出。R的最大值是2e9,L的最大值是1e9,R+L超过了int的范围。为什么第30行定义R的初值为2e9?请读者思考。
C++代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int LEN = 1e9;
int n, len;
int L[N], S[N];
bool check(int t){  // 检查t时刻,管道内是否都有水
    int cnt = 0;
    int last_L = 2, last_R = 1;
    for(int i = 0; i < n; i++)
        if(t >= S[i]){
            cnt++;      //特判t是否够大
            int left = L[i] - (t - S[i]);
            int right = L[i] + (t - S[i]);
            if(left < last_L)
                last_L = left, last_R = max(last_R, right);
            else if(left <= last_R + 1)
                last_R = max(last_R, right);
        }
    if(cnt == 0) return false;
    if(last_L <= 1 && last_R >= len)
        return true;
    else
        return false;
}
int main(){
    scanf("%d%d", &n, &len);
    for(int i = 0; i < n; i++)
        scanf("%d%d", &L[i], &S[i]);
    int L = 0, R = 2e9, ans = -1;
    while(L <= R){                      //二分
        int mid = ((R - L) >> 1) + L;   //如果写成(L+R)>>1可能溢出
        if(check(mid)) ans = mid, R = mid - 1;
        else           L = mid + 1;
    }
    printf("%d\n", ans);
    return 0;
}

Java代码

import java.util.Scanner;
public class Main {
    static int[] L;
    static int[] S;
    static int n;
    static int len;
    public static boolean check(int t) {
        int cnt = 0;
        int last_L = 2;
        int last_R = 1;
        for(int i = 0; i < n; i++) {
            if(t >= S[i]) {
                cnt++;
                int left = L[i] - (t - S[i]);
                int right = L[i] + (t - S[i]);
                if(left < last_L) {
                    last_L = left;
                    last_R = Math.max(last_R, right);
                } else if(left <= last_R + 1) {
                    last_R = Math.max(last_R, right);
                }
            }
        }
        if(cnt == 0) return false;
        if(last_L <= 1 && last_R >= len) return true;
        else return false;
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        len = scanner.nextInt();
        L = new int[n];
        S = new int[n];
        for(int i = 0; i < n; i++) {
            L[i] = scanner.nextInt();
            S[i] = scanner.nextInt();
        }
        int L = 0;
        int R = 2000000000;
        int ans = -1;
        while(L <= R) {
            int mid = ((R - L) >> 1) + L;
            if(check(mid)) {
                ans = mid;
                R = mid - 1;
            } else    L = mid + 1;            
        }
        System.out.println(ans);
    }
}

python代码

def check(t):
    cnt = 0
    last_L = 2
    last_R = 1
    for i in range(n):
        if t >= S[i]:
            cnt += 1
            left = L[i] - (t - S[i])
            right = L[i] + (t - S[i])
            if left < last_L:
                last_L = left
                last_R = max(last_R, right)
            elif left <= last_R + 1:
                last_R = max(last_R, right)
    if cnt == 0:      return False
    if last_L <= 1 and last_R >= length:   return True
    else:        return False
 
n, length = map(int, input().split())
L = []
S = []
for _ in range(n):
    l, s = map(int, input().split())
    L.append(l)
    S.append(s)
L_val,R_val =0, int(2e9)
ans = -1
while L_val <= R_val:
    mid = (R_val + L_val) >> 1
    if check(mid):
        ans = mid
        R_val = mid - 1
    else:  L_val = mid + 1 
print(ans)

4. 习题

答疑 https://www.lanqiao.cn/problems/1025/learning/
身份证 https://www.lanqiao.cn/problems/3849/learning/
翻硬币 https://www.lanqiao.cn/problems/209/learning/
防御力 https://www.lanqiao.cn/problems/226/learning/
合并果子 https://www.luogu.com.cn/problem/P1090
排队接水 https://www.luogu.com.cn/problem/P1223
小A的糖果 https://www.luogu.com.cn/problem/P3817
负载平衡问题 https://www.luogu.com.cn/problem/P4016
找零钱 https://www.lanqiao.cn/problems/3854
01搬砖 https://www.lanqiao.cn/problems/2201/learning/
卡牌游戏 https://www.lanqiao.cn/problems/1057/learning
寻找和谐音符 https://www.lanqiao.cn/problems/3975/learning
三国游戏 https://www.lanqiao.cn/problems/3518/learning/
平均 https://www.lanqiao.cn/problems/3532/learning/
小蓝的旅行计划 https://www.lanqiao.cn/problems/3534/learning/
排座椅 https://www.luogu.com.cn/problem/P1056
母舰 https://www.luogu.com.cn/problem/P2813
加工生产调度 https://www.luogu.com.cn/problem/P1248

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/263962.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构---算法的空间复杂度

文章目录 空间复杂度概念实例 空间复杂度 概念 空间复杂度也是一个数学表达式&#xff0c;是对一个算法在运行过程中临时占用存储空间大小的量度 。 空间复杂度不是程序占用了多少bytes的空间&#xff0c;因为这个也没太大意义&#xff0c;所以空间复杂度算的是变量的个数。…

【分享】4个方法打开PDF文件

PDF是很多人工作中经常使用的电子文档格式&#xff0c;但是可能有些刚接触的小伙伴不知道用什么工具来打开PDF文件&#xff0c;今天小编就来分享一下4种常用的工具。 1. 使用浏览器 只要有电脑基本都会安装一到两款浏览器&#xff0c;其实浏览器也可以用来打开PDF文件。 只需…

慢调用链诊断利器-ARMS 代码热点

作者&#xff1a;铖朴、义泊 可观测技术背景 从最早的 Google 发表的一篇名为《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》的论文开始&#xff0c;到后来以&#xff1a;Metrics&#xff08;指标&#xff09;、Tracing&#xff08;链路追踪&#xf…

深入理解依赖反转原则(DIP)

依赖反转原则是一个比较重要的架构原则&#xff0c;从定义上看是要依赖于抽象&#xff0c;不要依赖于细节&#xff0c; 这个听起来很简单&#xff0c;好像加个接口就完事了&#xff0c;大家的service都是一个接口配一个实现类&#xff0c;是不是依赖倒置呢&#xff1f;很显然不…

MyBatis关联查询(二、一对多查询)

MyBatis关联查询&#xff08;二、一对多查询&#xff09; 需求&#xff1a;查询所有用户信息及用户关联的账户信息。 分析&#xff1a;用户信息和他的账户信息为一对多关系&#xff0c;并且查询过程中如果用户没有账户信息&#xff0c;此时也要将用户信息查询出来&#xff0c…

【Amazon 实验①】Amazon WAF功能增强之实验环境准备

文章目录 1. 实验介绍2. 实验环境准备 1. 实验介绍 在真实的网络空间中&#xff0c;攻击者会使用大量广泛分布的僵尸网络、肉机等发起对目标的攻击。 其来源分布一般比较分散&#xff0c;因此难以简单防范。 本实验联合使用有多种AWS服务&#xff1a;Cloudfront、 Lambdaedge…

Python 字典操作函数 pop 与 popitem 的区别

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python中&#xff0c;字典&#xff08;dictionary&#xff09;是一种常用的数据结构&#xff0c;而对字典进行操作的函数也是开发中的重要工具。本文将深入探讨字典操作函数中的 pop 和 popitem 两个方法&…

C++的面向对象学习(4):对象的重要特性:构造函数与析构函数

文章目录 前言&#xff1a;将定义的类放在不同文件夹供主文件调用的方法一、构造函数与析构函数1.什么是构造函数和析构函数&#xff1f;2.构造函数和析构函数的语法3.构造函数的具体分类和调用方法①总的来说&#xff0c;构造函数分类为&#xff1a;默认无参构造、有参构造、拷…

word导入导出-Apache POI 和 Poi-tl

word 文件读取 使用Apache POI Word 进行读取文件 使用poi 时如果报ClassNotFoundException 等错误&#xff0c;请注意请求以下maven 文件的版本 Apache POI Word 说明文档&#xff1a;Apache POI Word 说明文档 maven 解决依赖冲突教程&#xff1a;https://www.cnblogs.com/…

[AutoSar]状态管理(五)Dcm与BswM、EcuM的复位实现

目录 关键词平台说明前言一、总体流程二、配置2.1 DCM and DEM2.2 BSWM2.2.1 Mode Notifaication Port2.2.2 Rules 2.3 service port2.3.1 做好DCM-->BSWM 和DCM -->SWC_Diag 的server port mapping2.3.2 做好BSWM ESH_ModeNotification 的server port mapping 2.4 SWC 中…

【Qt之Quick模块】5. QML基本类型及示例用法

QML格式 QML基本类型 在 QML 中&#xff0c;有以下基本类型&#xff1a; int&#xff1a;整数类型。 Rectangle {function myFunction() {// 输出 debug 信息console.log("11 " (11));}Component.onCompleted: {myFunction();} }结果&#xff1a; 2. real&…

PHP数组定义和输出

数组就是一组数据的集合&#xff0c;把一系列数据组织起来&#xff0c;形成一个可操作的整体。 PHP中的数组与Java的数组不一样&#xff0c;需要有key&#xff08;键&#xff09;和value&#xff08;值&#xff09;&#xff0c;相当于Java中数组和键值对的结合。 数组的定义 …

zynqmp Linux + 裸机 (A53-0 Linux,A53-1 2 3 裸机大数据量实时处理,R5-0 协议处理,R5-1 屏幕显示逻辑等)填坑笔记

fpga 和arm 采用预留内存的方式&#xff0c;采用neon 协处理器只能做到 250M/S 的速度&#xff0c;预留内存采用mmap的方式&#xff0c;当读取内存页的时候采用缺页中断的方式&#xff0c;导致速度拖沓而且预留内存没有进行Linux系统的内存管理&#xff08;在系统内 memcpy的速…

JMeter常见错误分析

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

《PySpark大数据分析实战》-17.云服务模式Databricks介绍运行作业

&#x1f4cb; 博主简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是wux_labs。&#x1f61c; 热衷于各种主流技术&#xff0c;热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员&#xff08;PCTA&#xff09;、TiDB数据库专家&#xff08;PCTP…

数据结构之进阶二叉树(二叉搜索树和AVL树、红黑树的实现)超详细解析,附实操图和搜索二叉树的实现过程图

绪论​ “生命有如铁砧&#xff0c;愈被敲打&#xff0c;愈能发出火花。——伽利略”&#xff1b;本章主要是数据结构 二叉树的进阶知识&#xff0c;若之前没学过二叉树建议看看这篇文章一篇掌握二叉树&#xff0c;本章的知识从浅到深的对搜索二叉树的使用进行了介绍和对其底层…

【经典LeetCode算法题目专栏分类】【第8期】滑动窗口:最小覆盖子串、字符串排列、找所有字母异位词、 最长无重复子串

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推荐--…

110基于matlab的混合方法组合的极限学习机和稀疏表示进行分类

基于matlab的混合方法组合的极限学习机和稀疏表示进行分类。通过将极限学习机&#xff08;ELM&#xff09;和稀疏表示&#xff08;SRC&#xff09;结合到统一框架中&#xff0c;混合分类器具有快速测试&#xff08;ELM的优点&#xff09;的优点&#xff0c;且显示出显着的分类精…

网安面试三十道题(持续更新)(sql注入系列)

61 给你一个网站&#xff0c;一般怎么做渗透测试的 先确定黑盒测试还是白盒测试 黑盒测试 信息收集&#xff1a; 服务器相关---&#xff1a;系统版本&#xff0c;真实IP&#xff0c;开放端口&#xff0c;使用的中间件 指纹信息---有无cdn加速&#xff0c;dns解析记录&#xff0…

ARM GIC(三) gicv2架构

ARM的cpu,特别是cortex-A系列的CPU,目前都是多core的cpu,因此对于多core的cpu的中断管理,就不能像单core那样简单去管理,由此arm定义了GICv2架构,来支持多核cpu的中断管理 一、gicv2架构 GICv2,支持最大8个core。其框图如下图所示: 在gicv2中,gic由两个大模块组成: …