题目描述
数轴上有 n (1<=n<=25000)个闭区间 [ai, bi],选择尽量少的区间覆盖一条指定线段 [1, t] (1<=t<=1,000,000)。覆盖整点,即(1,2)+(3,4)可以覆盖(1,4)。不可能办到输出-1。
输入
第一行:N和T 第二行至N+1行: 每一行一个闭区间。 |
输出
输出选择的区间的数目, 不可能办到输出-1 |
样例输入
3 10
1 7
3 6
6 10
样例输出
2
思路
综述
这个题和这周做的B题十分类似,也是将一个区间组合排序,然后依次选择;用到的算法也是贪心算法;
排序的准则是:按照按照右端点从头到尾的升序排列;
贪心的准则:
假定当前要覆盖的区间是[begin,end];闭区间)
下一次选择的区间满足以下条件:
1)区间的末端点大于于等于end;
2)区间的左端点是满足条件1中的最小的一个
如果某次选择没有可用区间,并且要覆盖的区间还未空,则退出返回-1,否则返回选择的区间的个数;
过程
Step1:输入
再输入的时候,进行一遍筛选,将右端点小于begin或者左端点大于end的清除
for (i = 0; i < n; i++) {
P xx;
cin >> xx.a >> xx.b;
if (xx.a<1 || xx.b>t) continue;
qq.push(xx);
}
Step2:选择
第一步判断
右端点最大的是否大于等于end
if (qq.top().b < t) {
std::cout << "-1" << endl;
return 0;
}
循环选择:
while (!qq.empty()) {
if (qq.top().b >= t) {//找到右端点在end之后的
if (qq.top().a < flag)
flag = qq.top().a;//找出符合的点中左端点最小的一个
if (flag <= 1) {
total++;//已经覆盖整个区间,退出并且输出结果
std::cout << total << endl;
return 0;
}
qq.pop();
}
else {
//找到之后进行更新
t = flag - 1;
flag = t;
total++;//区间选择总数加一
if (qq.top().b < t) {
std::cout << "-1" << endl;
return 0;
}
}
}
总结
1、使用快速读入scanf可以节省时间
2、贪心的准则有很多,需要找出并且证明一个正确的
记录:(刚开始错误的想法)
错误1:在多关键字排序的时候,排序的准则如下
1、右端点需要大于等于t
2、左端点小的排在前面
但是这样是错误的,因为更新过一次之后,再选择区间的时候,就会选择出并不符合题意的区间
错误2:在错误1的基础上优化,但是优化方式不对,导致超时
错误优化:每次选择完区间都进行重新排序,右端点大于等于当前要覆盖的区间的end,这样复杂度在O(n^2*log)级别上,严重超时。
代码
#include <iostream>
#include <stdio.h>
#include <queue>
using namespace std;
int t;
int total = 0;
struct P {
int a, b;
bool operator <(const struct P& p)const {
return b < p.b;
}
};
int main() {
int n, i;
cin >> n >> t;
priority_queue<P> qq ;
//读入数据
for (i = 0; i < n; i++) {
P xx;
cin >> xx.a >> xx.b;
//初步筛选
if (xx.a<1 || xx.b>t) continue;
qq.push(xx);
}
int step = 1;
//初步判断
if (qq.top().b < t) {
std::cout << "-1" << endl;
return 0;
}
int flag = 1000000;
while (!qq.empty()) {
//选点
if (qq.top().b >= t) {
//找出最优的点
if (qq.top().a < flag)
flag = qq.top().a;
if (flag <= 1) {
total++;
std::cout << total << endl;
return 0;
}
qq.pop();
}
else {
t = flag - 1;
flag = t;
total++;//更新
if (qq.top().b < t) {
std::cout << "-1" << endl;
return 0;
}
}
}
//不行
std::cout << "-1" << endl;
}