试题 算法提高 线段和点
资源限制
内存限制:256.0MB C/C++时间限制:1.0s Java时间限制:3.0s Python时间限制:5.0s
问题描述
有n个点和m个区间,点和区间的端点全部是整数,对于点a和区间[b,c],若a>=b且a<=c,称点a满足区间[b,c]。
求最小的点的子集,使得所有区间都被满足。
输入格式
第一行两个整数n m
以下n行 每行一个整数,代表点的坐标
以下m行 每行两个整数,代表区间的范围
输出格式
输出一行,最少的满足所有区间的点数,如无解输出-1。
样例输入
5 5
2
6
3
8
7
2 5
3 4
3 3
2 7
6 9
样例输出
2
数据规模和约定
1<=n,m<=10000
0<=点和区间的坐标<=50000
错误思路
开始思考到的是一个接口与对象的模型,如
在java语言中,某系统存在{O1, O2, O3… , On}共n个对象,同时,全部对象共实现了{I1,I2,I3……,Im} 共m个接口,各个接口之间独立,其中一个对象能实现多个接口,为了确保系统的精简,请在找出对象集合中找出最小子集实现了全部的接口。
开始我就当是这么的思路,先找出实现最多接口的对象,然后对接口进行标记已实现
,最后求出子集,但是却忽略了区间之间存在了包含,或者半包含的关系,所以这个思路的前提是区间之间相互独立,代码写出来了,错误的
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Range {
int min;
int max;
bool isSign;
} typedef Range;
struct Point {
int value;
vector<int> rangeIndex; // 可代表的区间的坐标
} typedef Point;
/**
根据最小值进行排序 , 升序
**/
bool rangeSort(Range a, Range b) {
return a.min < b.min;
}
/**
根据点的大小进行排序 , 升序
**/
bool pointSortByValue(Point a, Point b) {
return a.value < b.value;
}
/**
根据点的区间数量进行排序 , 降序
**/
bool pointSortByNum(Point a, Point b) {
return a.rangeIndex.size() > b.rangeIndex.size();
}
int main() {
int n; // 点数
int m; // 区间数
cin >> n >> m;
vector<Point> ps(n);
vector<Range> rs(m);
for (int i = 0; i < n; i ++) {
cin >> ps[i].value;
}
sort(ps.begin(), ps.end(), pointSortByValue);
for (int i = 0; i < m; i ++) {
cin >> rs[i].min >> rs[i].max;
// 判断哪些点可以代表这个区间
for (int j = 0; j < n; j ++) {
if (ps[j].value >= rs[i].min && ps[j].value <= rs[i].max) {
ps[j].rangeIndex.push_back(i);
}
}
}
int res = 0;
sort(ps.begin(), ps.end(), pointSortByNum);
for (int i = 0; i < n; i++) {
bool isNeed = false;
for (int j = 0; j < ps[i].rangeIndex.size(); j++) {
int index = ps[i].rangeIndex[j];
if (!rs[index].isSign) {
isNeed = true;
rs[index].isSign = true;
}
}
if (isNeed) {
res ++;
}
}
for (int i = 0; i < m; i ++) {
if (!rs[0].isSign) {
// 存在区间为标记
cout << -1 << endl;
return 0;
}
}
cout << res << endl;
return 0;
}
正确的思路
参考
[蓝桥杯][算法提高VIP]线段和点(排序+贪心)_n个点m个区间求区间长度_starlet_kiss的博客-优快云博客
结论
区间之间可以进行排序,即
区间起点小的在前面,区间起点一样,区间终点小的在前面
如排序过后:
- [ ]
- [ ]
- [ ]
- [ ]
排序的作用是,不需要对已代表的区间进行标记
不通过排序的方式,则需要对区间进行标记,而且这标记要考虑到不同的区间组合,实现难度挺大的
代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Range {
int x1;
int x2;
} typedef Range;
bool mySort (Range a, Range b){
if (a.x1 == b.x1) {
return a.x2 < b.x2;
}
return a.x1 < b.x1;
}
int main() {
int n;
int m;
cin >> n >> m;
vector<int> x(n);
vector<Range> r(m);
for (int i = 0; i < n; i ++) {
cin >> x[i];
}
for (int i = 0; i < m; i ++) {
cin >> r[i].x1 >> r[i].x2;
}
/*
排序后会是这样
1. [ ]
2. [ ]
3. [ ]
4. [ ]
*/
sort(r.begin(), r.end(), mySort);
int count = 0;
int st = 0; // 设置遍历区间的起点
while (count < n) {
// 遍历每个点,搜索能从最开始(非第一个)一直满足到最大长度的点,记录这点
// 设置根据这个最大长度,修改下一次的开始
int max = 0;
// 遍历点
for (int i = 0; i < n; i++) {
int _max = 0;
// 遍历区间
for (int j = st; j < m; j++) {
if (x[i] < r[j].x1 || x[i] > r[j].x2) {
break;
}
_max ++;
}
max = max > _max ? max : _max; // 取两个之间最大的
}
st += max; // 设置下次的起点
// 说明当前所有的区间已经完成
if (st == m) {
break;
}
count ++;
}
if (count == n) {
cout << -1 << endl;
} else {
cout << count + 1 << endl;
}
return 0;
}
总结
以后遇到类似两个集合,一个集合可以映射到另一个集合,即
y = f(x)
求x
的最小子集可以映射全部y
时,
独立
- 独立则可以用贪心算法,先找出最多映射关系的
x
,判断x
到y
的映射是否已全部被映射,不是则将x
放入结果集
不独立
-
试着将
y
的集合进行排序,排序的规则是,当
x
无法映射到当前y
时,则一定无法映射到后面的y
-
重复遍历
x
,每次遍历x时,x
都实现一部分的y
,得到最大映射次数的x
被认为属于结果集中,同时这个最大次数n
会被认为做排序后的前n
个y
被该x
实现,下次搜索时需要将以实现的y
剔除搜索集 -
直到所有的
y
均被实现