贪心专题(例题)

目录

线段(最大不相交区间数问题)

活动安排3(最大不相交区间数问题)

种树

喷水装置3(区间完全覆盖问题)


                                                             

线段

时间限制: 1 Sec  内存限制: 128 MB
提交: 27  解决: 10
[提交] [状态] [讨论版] [命题人:admin]

题目描述

数轴上有n条线段,选取其中k条线段使得这k条线段两两没有重合部分,问k最大为多少。

输入

第一行为一个正整数n;
在接下来的n行中,每行有2个数ai,bi,描述每条线段。

输出

输出一个整数,为k的最大值。

样例输入

3
0 2
2 4
1 3

样例输出

2

提示

对于20%的数据,n≤10;
对于50%的数据,n≤10^3;
对于70%的数据,n≤10^5;
对于100%的数据,n≤10^6,0≤ai<bi≤10^6。

                                                                              [提交][状态]

题解:

贪心中的最大不相交区间数问题,要使选取的线段数量最多,只要在满足与前一个线段不重合的基础上,每次选择右端点靠前的线段,这样就能使余下的的数轴更长,选择余地更大

#include <bits/stdc++.h>
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 1e9+5;
const int inf=1<<30;
struct node
{
    int l;
    int r;
}p[1000005];
int cmp(node x,node y)
{
    return x.r<y.r;
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d%d",&p[i].l,&p[i].r);

    sort(p+1,p+1+n,cmp);//按右端点从小到大排序
    int cnt=0;
    int t=0;//数轴起始点
    for(int i=1;i<=n;i++)if(p[i].l>=t)cnt++,t=p[i].r;

    printf("%d\n",cnt);
    return 0;
}

活动安排3

时间限制: 1 Sec  内存限制: 128 MB
提交: 72  解决: 27
[提交] [状态] [讨论版] [命题人:admin]

题目描述

设有n个活动的集合E={1,2,..,n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si<fi。如果选择了活动i,则它在时间区间[si,fi)内占用资源。若区间[si,fi)与区间[sj,fj)不相交,则称活动i与活动j是相容的。也就是说,当fi≤sj或fj≤si时,活动i与活动j相容。选择出由互相兼容的活动组成的最大集合。

输入

第一行一个整数n(1≤n≤1000);
接下来的n行,每行两个整数si和fi。

输出

输出互相兼容的最大活动个数。

样例输入

4
1 3
4 6
2 5
1 7

样例输出

2

                                                                                  [提交][状态]

题解:

这个题和上一个题线段一样,按照右端点从小到大排序

记录的时候 可以记录初始下标然后每次用后一个的左端端点和前一个的右端点比较即可

#include <bits/stdc++.h>
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 1e9+5;
const int inf=1<<30;
struct node
{
    int x;
    int y;
}a[1000005];
int cmp(node a,node b)
{
    return a.y<b.y;
}
int main()
{
    scanf("%d",&n);

    for(int i=0;i<n;i++)scanf("%d%d",&a[i].x,&a[i].y);
 
    sort(a,a+n,cmp);
 
    int j=0;
    int ans=1;
    for(int i=1;i<n;i++)
    {
        if(a[j].y<=a[i].x)
        {
            ans++;
            j=i;
        }
    }
    printf("%d\n",ans);
    return 0;
}

种树

时间限制: 1 Sec  内存限制: 128 MB
提交: 122  解决: 58
[提交] [状态] [讨论版] [命题人:admin]

题目描述

 一条街的一边有几座房子。因为环保原因居民想要在路边种些树。路边的地区被分割成块,并被编号成1..N。每个部分为一个单位尺寸大小并最多可种一棵树。每个居民想在门前种些树并指定了三个号码B,E,T。这三个数表示该居民想在B和E之间最少种T棵树。当然,B≤E,居民必须记住在指定区不能种多于区域地块数的树,所以T≤E-B+l。居民们想种树的各自区域可以交叉。你的任务是求出能满足所有要求的最少的树的数量。
写一个程序计算最少要种树的数量。

输入

第一行包含数据N,区域的个数(0<N≤30000);
第二行包含H,房子的数目(0<H≤5000);
下面的H行描述居民们的需要:B E T,0<B≤E≤30000,T≤E-B+1。

输出

树的数目。

样例输入

9
4
1 4 2
4 6 2
8 9 2
3 5 2

样例输出

5

                                                                                      [提交][状态]

题解:

满足所有居民种树要求时所需要种的树的数量最少 就要尽量使树种在两个区间的重合部分

而重叠位置一定在尾部。先按所有区间的结束位置排序

还是以右端点从小到大进行排序

#include <bits/stdc++.h>
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 1e9+5;
const int inf=1<<30;
struct node
{
    int b;
    int e;
    int t;
}p[5005];
int cmp(node a,node b)
{
    return a.e<b.e;
}
int vis[30005];
int main()
{
    int n;
    int h;
    scanf("%d%d",&n,&h);
    for(int i=1;i<=h;i++)scanf("%d%d%d",&p[i].b,&p[i].e,&p[i].t);

    sort(p+1,p+h+1,cmp);

    ll ans=0;
    for(int i=1;i<=h;i++)
    {
        int cnt=0;
        for(int j=p[i].e;j>=p[i].b;j--)if(vis[j])cnt++;///扫一遍当前区间看种了多少棵树
        if(cnt<p[i].t)///如果当前区间种的数小于所要求的数那么把少的补上即可
        {
            int tt=p[i].t-cnt;
            ans+=tt;
            for(int j=p[i].e;j>=p[i].b;j--)
            {
                if(!vis[j])
                {
                    if(!tt)break;
                    vis[j]=1;
                    tt--;
                }
            }
        }
    }
    printf("%lld\n",ans);
    return 0;
}

喷水装置3

时间限制: 1 Sec  内存限制: 128 MB
提交: 38  解决: 8
[提交] [状态] [讨论版] [命题人:admin]

题目描述

长L米,宽W米的草坪里装有n个浇灌喷头。每个喷头都装在草坪中心线上(离两边各W/2米)。我们知道每个喷头的位置(离草坪中心线左端的距离),以及它能覆盖到的浇灌范围。
请问:如果要同时浇灌整块草坪,最少需要打开多少个喷头?

 

输入

输入包含若干组测试数据。
第一行一个整数T表示数据组数;
每组数据的第一行是整数n、L和W(n≤15000);
接下来的n行,每行包含两个整数,给出一个喷头的位置和浇灌半径(上面的示意图是样例输入第一组数据所描述的情况)。

 

输出

对每组测试数据输出一个数字,表示要浇灌整块草坪所需喷头数目的最小值。如果所有喷头都打开也不能浇灌整块草坪,则输出 -1

样例输入

3
8 20 2
5 3
4 1
1 2
7 2
10 2
13 3
16 2
19 4
3 10 1
3 5
9 3
6 1
3 10 1
5 3
1 1
9 1

样例输出

6
2
-1

                                                                                       [提交][状态]

题解:

喷水装置是一道非常好的贪心经典题,属于“区间覆盖问题”

我们可以把题目简化成这样:

给定一条线段(区间)l(其端点为A, B)和 k个区间(p,q) , 请选用最少的区间覆盖这一条线段(区间)

解决

先思考这样一个问题: 
如何选用最少的小区间覆盖一条线段(大区间)? 

如上图所示,用小区间[px,qx]覆盖目标区间[A,B], 
选择区间[q1,p1]和[q4,p4](即小线段1和4)一定要比选择[q1,p1]和[q6,p6]要好——选择[q4,p4]能够占据更多的空间,而且选择都能够占据点p1,使得其前面的所有空间连贯起来。 
推而广之,得到:

贪心策略

将所有的区间按照左端点从小到大排序,选择覆盖点A的区间中右端点最大的一个,并将A更新为这个区间右端点的坐标,再次选择覆盖点A的区间中右端点最大的一个,将A更新为这个区间右端点的坐标,如此往复,直到覆盖了点B 
如下图所示:

总而言之

给定一条线段(区间)l(其端点为A, B)和 k个区间(p,q) , 请选用最少的区间覆盖这一条线段(区间)

按照左端点从小到大排序即可实现

读入的是喷水装置(即一个圆)的半径r, 而不是覆盖的范围,覆盖范围应该是:
a[k].l=o−sqrt(r∗r−(w/2)∗(w/2))
a[k].r=o+sqrt(r∗r−(w/2)∗(w/2))

因为只有这样扫描草坪才能将草坪完全覆盖,如果只是看两个相切的圆有部分覆盖不到所以选择区间的左右端点是它的弦


这里l和r是区间的左右端点,o为喷水装置的坐标,即喷水装置喷水范围的圆心,r为其半径,w为草坪的宽度
如果喷水装置的半径小于草坪的宽度,那么这个喷水装置肯定不能用,因为它一块区间都覆盖不了

还要注意精度 double
以上解析来自大佬W_px  

需要注意的是这种记录的方法

用一个变量记录从开始到当前区间的区间最大值,一个变量记录左端点在上一个区间最大值的范围内,右端点的最大值

然后更新即可

#include <bits/stdc++.h>
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 1e9+5;
const int inf=1<<30;
struct node
{
    double l;
    double r;
} p[15005];
int cnt;
int cmp(node a,node b)
{
    return a.l<b.l;
}
int main()
{

    int t;
    int o;
    double r;
    int n,l;
    double w;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%lf",&n,&l,&w);
        cnt=0;
        for(int i=0; i<n; i++)
        {
            scanf("%d%lf",&o,&r);
            if(r<=w/2)continue;//如果喷水装置的半径小于草坪的宽度,那么这个喷水装置肯定不能用,因为它一块区间都覆盖不了
            p[++cnt].l=o-sqrt(r*r-w*w/4.0);
            p[cnt].r=o+sqrt(r*r-w*w/4.0);

        }

        sort(p+1,p+cnt+1,cmp);
        int ans=0;
        int flag=1;
        double tt=0.0;///前pos个区间内能覆盖的最大区间
        int pos=1;
        while(tt<l)
        {
            int i;
            double temp=0.0;
            for(i=pos;i<=cnt && p[i].l<=tt;i++)
                temp=max(temp,p[i].r);///左边界在tt内的能覆盖最远的距离
            if(temp>tt)
            {
                ans++;
                tt=temp;
                pos=i;
            }
            else// 如果左边界在tt内覆盖的最远范围小于当前的范围输出-1
            {
                printf("-1\n");
                flag=0;
                break;
            }
        }
        if(flag)printf("%d\n",ans);
    }
    return 0;
}


 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值