贪心-----区间问题

贪心是指每次找局部最优解,就可以找到全局最优解,所以贪心一般要求函数是单峰的。

动态规划的限制会少一些,一般是枚举了空间中的所有值,找出了最优解。

一、区间选点

给定 N 个闭区间 [ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。

输出选择的点的最小数量。

位于区间端点上的点也算作区间内。

输入格式

第一行包含整数 N,表示区间数。

接下来 N 行,每行包含两个整数 ai,bi,表示一个区间的两个端点。

输出格式

输出一个整数,表示所需的点的最小数量。

数据范围

1≤N≤10^5,
−10^9≤ai≤bi≤10^9

输入样例:

3
-1 1
2 4
3 5

输出样例:

2

 分析:1.将每个区间按右端点从小到大排序
            2.从前往后枚举每个区间、
            (1)如果当前区间已经包含点,则直接pass
            (2)否则,选择当前区间的右端点

ans表示我们想要的最优解

cnt表示选择的区间个数

证明 : ans<=cnt

这个证明用的是2.(1)条件:

cnt表示当前选择一种可行性方案解,所选的区间的个数

因为cnt是所有可行性方案,所以ans<=cnt

证明 : ans>=cnt

这个证明用的是2.(2)条件:

因为我们所有区间要满足2.(2)的条件,那么,所有区间是没有重合的部分的,我们要想包含所有区间,就必须选择所有区间,区间个数是cnt

那么我们任何一个方案,点的个数必须大于等于cnt,点的个数可以超过cnt,因为这时,满足题意每个区间至少有一个点。所以ans>=cnt

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;

struct Range{
    int l,r;
    bool operator < (const Range &W) const
    {
        if(r==W.r) return l<W.l;
        return r<W.r;
    }
}range[N];
int n;
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++) 
    scanf("%d%d",&range[i].l,&range[i].r);
    sort(range,range+n);
    int ed=-2e9;
    int res=0;
    for(int i=0;i<n;i++)
    {
        if(ed<range[i].l)
        {
            res++;
            ed=range[i].r;
        }
    }
    cout<<res<<endl;
    return 0;
}

 二、最大不相交区间数量

给定 N 个闭区间 [ai,bi],请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。

输出可选取区间的最大数量。

输入格式

第一行包含整数 N,表示区间数。

接下来 N 行,每行包含两个整数 ai,bi,表示一个区间的两个端点。

输出格式

输出一个整数,表示可选取区间的最大数量。

数据范围

1≤N≤105,
−109≤ai≤bi≤109

输入样例:

3
-1 1
2 4
3 5

输出样例:

2

此题可以对应的实际问题:同时有很多课,我们想要选尽可能多的课。 

分析:和第一题一样,第一题是按右端点排序,如果出现一个区间的左端点大于此时区间的右端点,那么res++;

而这一题:选尽可能多的区间,那么如果一个区间的左端点比当前枚举区间的右端点小,那么这两个区间一定不能同时选,所以和第一题代码一样。 

代码

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;

struct Range{
    int l,r;
    bool operator < (const Range &W) const{
        return r<W.r;
    }
}range[N];

int n;
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%d%d",&range[i].l,&range[i].r);
    sort(range,range+n);
    int res=0,ed=-2e9;
    for(int i=0;i<n;i++)
    {
        if(ed<range[i].l)
        {
            res++;
            ed=range[i].r;
        }
    }
    cout<<res<<endl;
    return 0;
}

三、区间分组

(1)

给定 N 个闭区间 [ai,bi],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。

输出最小组数。

输入格式

第一行包含整数 N,表示区间数。

接下来 N 行,每行包含两个整数 ai,bi,表示一个区间的两个端点。

输出格式

输出一个整数,表示最小组数。

数据范围

1≤N≤105,
−109≤ai≤bi≤109

输入样例:

3
-1 1
2 4
3 5

输出样例:

2

有若干个活动,第i个活动开始时间和结束时间是[Si,fi],同一个教室安排的活动之间不能交叠,求要安排所有活动,少需要几个教室?

有时间冲突的活动不能安排在同一间教室,与该问题的限制条件相同,即最小需要的教室个数即为该题答案。

注意题目:不能有交集 

也就是说[1,4],[4,5],答案为2;

变为{2,8,9,11};

本题可以巧妙的处理起点和终点相同的问题

巧妙做法代码

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int a[N*2],idx;

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        a[idx++]=x*2;//左端点全部设为偶数
        a[idx++]=y*2+1;//右端点全部设为奇数
    }
    sort(a,a+idx);
    int res=0,t=0;
    for(int i=0;i<idx;i++)
    {
        if(a[i]%2==0) t++;
        else t--;
        res=max(res,t);
    }
    cout<<res<<endl;
    return 0;
}

朴素做法

#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 1e5 + 10;
struct Range{
    int l,r;
    bool operator < (const Range &W) const{
        return l<W.l;
    }
}range[N];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    scanf("%d%d",&range[i].l,&range[i].r);
    sort(range,range+n);
    priority_queue<int,vector<int>,greater<int>> heap;//小根堆的作用是每次比较时,用的都是所有组中最小的右端点
    for(int i=0;i<n;i++)
    {
        if(heap.empty()||heap.top()>=range[i].l) heap.push(range[i].r);
        else
        {
            heap.pop();
            heap.push(range[i].r);
        }
    }
    cout<<heap.size()<<endl;
    return 0;
}


Schedules (nowcoder.com)https://ac.nowcoder.com/acm/problem/25346

本题是 可以有交集的,即时间为[1,4],[4,5],答案为1;

代码

#include <bits/stdc++.h>
using namespace std;
int vis[100005];
int main() {
    int n, l, r, maxx = 0, max_ = 0;
    scanf("%d",&n);
    for (int i = 0; i < n; i++) {
        scanf("%d%d", &l, &r);
        max_ = max(max_, r);
        vis[l]++;
        vis[r]--;
    }
    for (int i = 1; i <= max_; i++) {
        vis[i] += vis[i - 1];
        maxx = max(maxx, vis[i]);
    }
    printf("%d\n", maxx);
}

四、区间覆盖

给定 N 个闭区间 [ai,bi] 以及一个线段区间 [s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。

输出最少区间数,如果无法完全覆盖则输出 −1。

输入格式

第一行包含两个整数 s 和 t,表示给定线段区间的两个端点。

第二行包含整数 N,表示给定区间数。

接下来 N 行,每行包含两个整数 ai,bi,表示一个区间的两个端点。

输出格式

输出一个整数,表示所需最少区间数。

如果无解,则输出 −−1。

数据范围

1≤N≤105,
−109≤ai≤bi≤109,
−109≤s≤t≤109

输入样例:

1 5
3
-1 3
2 4
3 5

输出样例:

2

算法思路:

1.先把所有区间按照左端点从小到大排序。

2.找到满足条件的区间(区间的左端点小于等于线段的左端点),从所有满足条件的区间中找到右端点最大的区间。(这一步用到了贪心的思想)

代码

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
struct Range {
    int l,r;
    bool operator < (const Range &W) const
    {
        return l<W.l;
    }
}range[N];
int main()
{
    int n;
    int st,ed;
    scanf("%d%d",&st,&ed);
    scanf("%d",&n);
    for(int i=0;i<n;i++) 
    {
        int l,r;
        scanf("%d%d",&l,&r);
        range[i]={l,r};
    }
    
    sort(range,range+n);
    
    int res=0,flag=0;
    for(int i=0;i<n;i++)
    {
        int j = i , r=-2e9;//每次初始r的值,是因为当r=st,且下边已经没有区间时,不初始就不会break
        while(j < n && range[j].l <= st)
        {
            r=max(r,range[j].r);
            j++;
        }
        if(r < st)
        {
            res=-1;
            break;
        }
        res++;
        if(r>=ed)
        {
            flag=1;
            break;
        }
        st=r;
        i=j-1;
    }
    if(flag==0) res=-1;
    printf("%d\n",res);
    
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值