算法提高 线段和点

试题 算法提高 线段和点

资源限制

内存限制: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的博客-优快云博客

结论

区间之间可以进行排序,即

区间起点小的在前面,区间起点一样,区间终点小的在前面

如排序过后:

  1. [ ]
  2. ​ [ ]
  3. ​ [ ]
  4. ​ [ ]

排序的作用是,不需要对已代表的区间进行标记

不通过排序的方式,则需要对区间进行标记,而且这标记要考虑到不同的区间组合,实现难度挺大的

代码

#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时,

独立

  1. 独立则可以用贪心算法,先找出最多映射关系的x,判断xy的映射是否已全部被映射,不是则将x放入结果集

不独立

  1. 试着将y的集合进行排序,

    排序的规则是,当x无法映射到当前y时,则一定无法映射到后面的y

  2. 重复遍历x,每次遍历x时,x都实现一部分的y,得到最大映射次数的x被认为属于结果集中,同时这个最大次数n会被认为做排序后的前ny被该x实现,下次搜索时需要将以实现的y剔除搜索集

  3. 直到所有的y均被实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值