【问题】
区间覆盖问题(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 <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
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