【问题】

区间覆盖问题(Interval Cover Problem)常常又叫区间重合问题。

一般情况下是求最少区间覆盖,顾名思义,就是用最少数量的小区间去覆盖一个更大的区间。

但是本文所说的问题仅仅指的是:判断一个源区间能否被若干给定的已知区间覆盖,是个判断题。

【例题】

《编程之美》P211的“区间重合判断”就是一种区间覆盖问题。

题目:给定源区间[1,6]和一组无序的目标区间[2,3][1,2][3,9],即可认为区间[1,6]在区间[2,3][1,2][3,9]内。

【分析】

参见编程之美。主要分以下三步:

(1)排序。(2)合并。(3)搜索。(1和3具体请看编程之美书中的介绍)

下面我只是讲解我的对“合并”的理解。合并的具体方法其实就是“Two-finger”的算法。这个名词其实很好理解,其实在排序等算法中非常常见。下面稍微解释一下。

先举一个最简单的例子。求一个数组的最大值的max()函数的实现。那么我们需要用一个临时变量储存已经找到的最大值,同时遍历数组,如果找到比这个临时变量更大的数,就更新这个数。可以形象的把临时变量比喻成左边的手指,在数组中遍历的指针比喻成右边手指。这就是Two-finger的来历。

排序算法中也经常使用。比如冒泡排序中,我们的Two-finger就要指向相邻的两个元素,如果右边finger的数比左边的大,那么就交换两个finger的顺序。

字符串比较的诸多算法中也常见Two-finger的使用。

同理,在《编程之美》P178的问题中,也是用到了Two-finger算法。(请读者自行思考)

那么这个题目中如何使用Two-finger算法呢?(假设我们已经将这些区间按照起始位置排好序了)那么我们的left finger指向开始的区间,那么right finger指向下一个。分情况讨论:如果right finger所指可以合并到left finger所指的区间中,那么就改写left finger区间,然后right finger下移一位;如果right finger不能合并,那么left finger指向right finger,right finger下移一位。

这个过程很简单,但是请注意处理数组越界的情况(对照上面,有2种):

1、当最后一个区间是可以合并的,所以right finger一直合并到数组结尾。此时要判断是否越界,并保存left finger所指的区间。

2、当最后一个区间是不能合并的,此时left finger指向最后一个区间,但是此时right finger已经越界,但是仍然别忘记保存最后一个不能合并的(left finger所指的)区间。

【代码】

下面是我的一个解答代码(有详尽注释):

#include <iostream>
#include
<vector>
#include
<fstream>
#include
<string>
#include
<algorithm>

using namespace std;

struct
Interval
{
     Interval(
int l, int r ){ left = l ; right =
r; }
     Interval() { left
= 0 ; right = 0
; }
    
int
left;
    
int
right;
};

//pre-process the input intervals

void preprocess( vector<Interval>& v );
//judge if the given Interval is covered by those extended intervals

bool judge( struct Interval& a, vector<Interval>& v );

int
main()
{
     vector
<Interval>
v;
    
//input

    int num;
     ifstream input(
"input.txt"
);
     input
>>
num;
     Interval temp;
    
while ( num--
)
     {
         input
>>temp.left>>
temp.right;
         v.push_back( temp );
     }
     input
>>temp.left>>
temp.right;

     preprocess( v );
     cout
<<judge( temp, v )<<
endl;
}

static bool compare( Interval& a, Interval&
b )
{
    
return ( a.left <
b.left );
}

void preprocess( vector<Interval>&
v )
{
    
//sort by the start of each interval

     sort( v.begin(), v.end(), compare );

    
int size =
v.size();
    
/*

     ** save the merged intervals
     ** e.g. v = {[1,3],[2,4],[3,7]},then merged = {[1,7]}
     ** and finally, copy back to v
     *
*/

     vector
<Interval> extended;
    
//
Important LOOP!!! Extend!
    
// j is always the next one of i

    for ( int i = 0, j = 1 ; i <= size - 1 ; )
     {
        
        
/*

         ** e.g. v[i] = [2,7],then every other interval starts between 2 and 7
         ** their right end should be checked.
         *
*/
        
while ( v[j].left >= v[i].left && v[j].left <= v[i].right )
         {
            
//meet interval that can extend the current one, then extend

            if ( v[j].right > v[i].right )
                 v[i].right
=
v[j].right;
             j
++
;
            
//certain interval can be extended to the end

            if ( j == size )
             {
                 extended.push_back( Interval( v[i].left, v[i].right ) );
                 v
=
extended;
                
return
;
             }
         }
        
//Can not find any interval to extend, then save the current one

         extended.push_back( Interval( v[i].left, v[i].right ) );
        
        
// jump to the interval which can't extend current one

         i = j;
         j
++
;    
        
//copy back

        if ( j == size )
         {
             extended.push_back( Interval( v[i].left, v[i].right ) );
             v
=
extended;
            
return
;
         }
     }
}

bool judge( struct Interval& a, vector<Interval>&
v )
{
    
for ( size_t i = 0 ; i <= v.size()-1 ; i++
)
     {
        
//if the start of given interval falls in an known interval

        if ( a.left >= v[i].left && a.left <= v[i].right )
         {
            
//if given end is on the left of known interval, it's covered

            if ( a.right <= v[i].right )
                
return true
;
            
//if's not covered

            else
                
return false;
         }
     }
    
//the start of given interval falls in no known interval

    return false;
}

输入:input.txt

3
2 3
1 2
3 9
1 6

第一行是一个数字N,表示目标区间有多少个;以下N行就是目标区间的起点和终点。最后一行是给定的源区间。最后输出1就表示能覆盖,输出0表示不能覆盖。

【最少区间覆盖问题】

用最少的已知区间去覆盖给定区间。

参考:http://blog.csdn.net/zhangwenjun32/archive/2009/05/04/4149683.aspx

【参考习题】

有空的时候做一做吧

POJ 1716 Integer Intervals

http://acm.pku.edu.cn/JudgeOnline/problem?id=1716

POJ 2376 Cleaning Shifts

http://acm.pku.edu.cn/JudgeOnline/problem?id=2376




通过 Wiz 发布


posted on 2011-05-11 22:57  微型葡萄  阅读(1475)  评论(0编辑  收藏  举报