zoukankan      html  css  js  c++  java
  • 区间贪心问题小结(区间选点,区间覆盖,区间选取)

    1. 贪心算法

      思想:什么是贪心算法,什么算得上是贪心

      贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,只做出在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。

      例题:

      1. 最少硬币问题 有1、2、5、10、20、50、100七种面值的硬币,要支付指定的金额,问怎么支付所用的硬币个数最少

        策略:紧着最大分值换。

      2. 最大斜率问题 ,给出n个点的坐标(笛卡尔坐标) 求(A[i]-A[j])/(i-j)最大

        策略:相邻的坐标中找到最大斜率

      3. 区间选取(会场安排问题),给一个大区间l,r然后给你n个区间,最最多多少个区间没有重复部分

        例子:

        学校的小礼堂每天都会有许多活动,有时间这些活动的计划时间会发生冲突,需要选择出一些活动进行举办。小刘的工作就是安排学校小礼堂的活动,每个时间最多安排一个活动。现在小刘有一些活动计划的时间表,他想尽可能的安排更多的活动,请问他该如何安排。

        输入:

        第一行是一个整型数m(m<100)表示共有m组测试数据。
        每组测试数据的第一行是一个整数n(1<n<10000)表示该测试数据共有n个活动。
        随后的n行,每行有两个正整数Bi,Ei(0<=Bi,Ei<10000),分别表示第i个活动的起始与结束时间(Bi<=Ei)

        输出:

        对于每一组输入,输出最多能够安排的活动数量。

        策略:每选一个之后能给后面的留更多的时间(效果:按结束时间排序)

        那么第一个时,肯定选此时能选的结束时间最早的,选其他的话给后面留的时间都比前者小,所以咱们选的第一个肯定没错,就是此时能选的结束时间最早的,然后选第二个时,也是选可选时间中结束最早的,这样保证有其最优解,归纳起来激就是,每个根据当前可用时间,选取一个结束时间最早的,做为下一个会场的安排,

        题目链接:http://acm.nyist.cf/problem/14

        #include<stdio.h>
        #include<algorithm>
        using namespace std;
        const int maxn=10010;
        struct Node{
        int beg,end;
        }node[maxn];
        bool cmp(Node a,Node b)
        {
            return a.end<b.end;
        }
        int main()
        {
            int t,n,ans;
            scanf("%d",&t);
            while(t--)
            {
                scanf("%d",&n);
                for(int i=0;i<n;++i)//输入区间 并处理
                {
                    scanf("%d %d",&node[i].beg,&node[i].end);
                    node[i].end++;//将区间变为左闭右开
                }
                sort(node,node+n,cmp);//将区间按右端点排序,右端点小的在前面
                ans=0;
                int pos=0;//初始化
                //pos意为上一个选取的活动结束的位置,若果beg>=pos就可以安排
                for(int i=0;i<n;++i)
                {
                    if(node[i].beg>=pos)
                    {
                        ++ans;
                        pos=node[i].end;
                    }
                }
                printf("%d
        ",ans);
            }
        }
        
        
      4. 区间选点问题,n个闭区间[ai,bi],让他取尽量少的点,使得每个闭区间内至少有一个点。

        输入:

        n个闭区间,

        输出:

        最少用几个点,把每个区间都包含一个点

        策略:让这个点出现在一个没有点的区间上,尽可能覆盖多的区间的地方**(效果:按结束处排序)**

        首先为了将最左边的一个区间覆盖,(按结束排序即可)那么第一个点必须在第一个区间上,那么在区间上哪呢?为了让这个点让更多的区间的区间碰到,让这个点最靠右,这样的话能保证这个点覆盖的地方最多,然后一直往后遍历,直到一个区间不在这个点上时,为了让这个区间被覆盖,必须在从这个区间上找一点,(问题变为了前者) 每次一个点可以解决一个区间或者若干个区间,这遍历完所有区间即可

        链接 http://nyoj.top/problem/891

        代码:

        #include<stdio.h>
        #include<algorithm>
        using namespace std;
        const int maxn=10010;
        struct Node{
        int beg,end;
        }node[maxn];
        bool cmp(Node a,Node b)
        {
            return a.end<b.end;
        }
        int main()
        {
            int n,ans;
            while(~scanf("%d",&n))
            {
                for(int i=0;i<n;++i)//输入区间 并处理
                {
                    scanf("%d %d",&node[i].beg,&node[i].end);
                }
                sort(node,node+n,cmp);//将区间按右端点排序,右端点小的在前面
                ans=0;
                int pos=-1;//pos代表第一个区间选取的点
                for(int i=0;i<n;++i)
                {
                    if(node[i].beg>pos)
                    {
                        pos=node[i].end;
                        ++ans;
                    }
                }
                printf("%d
        ",ans);
            }
        }
        
        
      5. 区间完全覆盖问题

        问题描述:给定一个长度为m的区间(全部闭合),再给出n条线段的起点和终点(注意这里是闭区间),求最少使用多少条线段可以将整个区间完全覆盖

        将所有区间化作此区间的区间,剪辑一下(没用的区间删除)

        策略:在能连接区间左边的情况下,找到向右边扩展最长的位置。(效果:按开头排序,开头一样,右边最长的靠前)

        为了连接到这个需要被覆盖区间的左边,选一个左端点最靠前的区间,如果左端点相同让右端点大的排在前面

        然后向右扫描区间…,如何找下一个需要安置的区间呢,即直到找到与上一个区间没有连接的地方,这时候必须找一个区间来来作为一个连接,因为前面区间都没有断开,所以在前面扫描过的区间找到一个结束处最大的区间作为连接就行,记下这个能扩展到右边的最大位置(其实这个过程是找边的过程)。如果这个最大位置都不能连着,证明这个区间不能被完全覆盖!即不存在解。

        链接:http://nyoj.top/problem/12

        代码:

        #include<stdio.h>
        #include<string>
        #include<string.h>
        #include<stdlib.h>
        #include<algorithm>
        #include<math.h>
        using namespace std;
        const int maxn=10010;
        struct Node{
        double beg,end;
        }node[maxn];
        bool cmp(Node a,Node b)
        {
            if(a.beg==b.beg)
                return a.end>b.end;
            return a.beg<b.beg;
        }
        int main()
        {
            int t,n,cnt=0;
            double w,h;
            int ans=0;
            double x,r;
            scanf("%d",&t);
            while(t--)
            {
                scanf("%d %lf %lf",&n,&w,&h);
                cnt=0;
                while(n--)
                {
                    scanf("%lf %lf",&x,&r);
                    if(r<=h/2.0)//过滤掉无效的喷水装置
                        continue;
                    double ll,rr;//存下该喷水装置区间的范围
                    double mid=sqrt(r*r-(h*h/4.0));
                    ll=x-mid;
                    rr=x+mid;//将喷水装置转化为能覆盖的区间
                    ll=max(0.0,ll);
                    rr=min((double)w,rr);
                    node[cnt].beg=ll;
                    node[cnt].end=rr;
                    ++cnt;
                }
                /*
                此时转化为一个区间覆盖问题   即在一个长度为w的区间内  选出最少的区间让这个区间覆盖
                */
                node[cnt].beg=(double)w;
                node[cnt].end=(double)w;//加入一个终端区间[w,w]这样遍历到整个区间最后会找出来一个往右边延伸到w的位置的区间,如果没有就没答案
                ++cnt;
                sort(node,node+cnt,cmp);
                double maxpos,nowpos;
                nowpos=0.0;
                maxpos=0.0;
                int flag=1;//
                ans=0;
                for(int i=0;i<cnt;++i)
                {
                    if(node[i].beg<=nowpos)//这个区间可以与前面的区间连着
                        maxpos=max(maxpos,node[i].end);//更新课扩展的最大区间
                    else
                    {
                        if(maxpos>=node[i].beg)//遇到一个间隔的 需要找一个区间补一下
                        {
                            ans++;
                            nowpos=maxpos;
                            --i;
                        }
                        else//如果不能补
                        {
                            flag=0;
                            break;//无解
                        }
                    }
                }
                if(flag)
                    printf("%d
        ",ans);
                else
                    printf("0
        ");
            }
        }
        
  • 相关阅读:
    json转换字符串
    windows下Xshell远程访问虚拟机
    win7去箭头指令
    n核CPU为什么计算速度达不到单核n倍
    vim字符串的替换
    转发的别人的vim编码和终端编码的设置
    音频操作
    scanf函数
    文字常量区和栈区区别
    Linux 进程
  • 原文地址:https://www.cnblogs.com/dchnzlh/p/10427309.html
Copyright © 2011-2022 走看看