轴与区间与点的贪心问题
一:活动选择问题(轴上最大不相交区间问题)
1. 描述
给定若干个区间,你可以从中选择一些区间,要求选择的区间不相交,请问最多可以选择多少个区间?
2. 思路
以一种贪心思路想,从轴的左边往右想,第一个选择的区间应该是什么呢?
我们选择了第一个区间(\([l, r]\))之后,剩余的可选择区间都一定是在已经选择的区间右端点(\(r\))之后,那么后面的区间的可选择范围显然缩小了。为了后面可选择的区间尽可能的多,那么我们显然希望后面可选择的范围尽可能大。
换言之,如果我们选择的第一个区间的右端点(\(r\))尽可能小,就可以让后面的选择范围更大,更有可能选择更多的区间。
以此类推,第二、第三 …… 所有的区间都应该如此考虑。
3. 做法
(1)对所有区间进行排序,以区间的右端点为判断条件,右端点小的区间在前。
(2)记录已安放的区间的最右位置 x,循环枚举每一个区间,如果当前区间的左端点大于等于 x,那么这个区间可以放,答案加一,更新 x。
4. 例题
AC代码
// Problem: P1803 凌乱的yyy / 线段覆盖
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1803?contestId=287100
// Memory Limit: 512 MB
// Time Limit: 3000 ms
#include <bits/stdc++.h>
#define int long long
using namespace std;
#define endl '\n'
#define fi first
#define se second
#define pb push_back
#define PII pair<int, int>
#define lowbit(x) (x & (-x))
#define all(a) a.begin(), a.end()
#define debug(x) cout << #x << " = " << x << "\n";
#define vdebug(a) cout << #a << " = "; for(auto& x: a) cout << x << " "; cout << "\n";
#define vlrdebug(a, l, r) cout << #a << " = "; for(auto i = l; i <= r; i ++) cout << a[i] << " "; cout << "\n";
#define lc p << 1
#define rc p << 1 | 1
const int N = 1e6 + 10, M = 1010;
const int mod = 1e9 + 7, MOD = 998244353;
const int INF = 0x3f3f3f3f;
const long long inf = 0x3f3f3f3f3f3f3f3f;
const int dx[] = {0, 0, 1, -1}, dy[] = {1, -1, 0, 0};
int n, m, k;
int a[N];
void solve(){
cin >> n;
vector<PII> seq(n);
for(auto& it: seq) cin >> it.fi >> it.se;
sort(all(seq), [](const auto& u, const auto& v){
return u.se < v.se;
});
int ans = 0, idx = 0;
for(auto [l, r]: seq){
if(l >= idx) idx = r, ans ++;
}
cout << ans << endl;
}
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int _ = 1;
// cin >> _;
while(_ --) solve();
return 0;
}
AC代码
class Solution {
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if(intervals.size() == 0){
return 0;
}
sort(intervals.begin(), intervals.end(), [](const auto& u, const auto& v){
return u[1] < v[1];
});
int n = intervals.size();
int ans = 0, idx = intervals[0][0];
for(int i = 0; i < n; i ++){
if(intervals[i][0] >= idx) ans ++, idx = intervals[i][1];
}
return n - ans;
}
};
二:带权活动选择问题
1.描述
给定若干个区间,每个区间有一个价值,你可以从中选择一些区间,要求选择的区间不相交,请问选择的区间总价值最大为多少?
2.思路
显然这题我们就无法用贪心来求解,我们可以对它使用dp!不难发现其实这就是一个01背包变形,每一个区间只有选与不选。
首先,因为还是要求区间不重叠,所以还是要先对区间进行排序,根据区间右端点排序。
从左到右枚举,我们可以用dp[i]表示考虑前i个区间的最大价值。
如果不选第 i 个区间,那么 dp[i] = dp[i - 1];
如果选第 i 个区间,那么 dp[i] 就应该由前面与该区间不重叠的区间最大价值加上第 i 个区间的价值:dp[k] + v[i](k 为右端点小于第 i 个区间左端点的最右边区间编号)。
3.做法
先根据区间右端点进行排序,右端点小的在前。
然后枚举区间,根据dp状态转移式计算dp。
4.例题
AC代码
class Solution {
public:
int jobScheduling(vector<int>& startTime, vector<int>& endTime, vector<int>& profit) {
int n = startTime.size();
vector<vector<int>> jobs(n + 10);
for(int i = 1; i <= n; i ++){
jobs[i] = {startTime[i - 1], endTime[i - 1], profit[i - 1]};
}
sort(jobs.begin() + 1, jobs.begin() + n + 1, [](const auto& u, const auto& v){
return u[1] < v[1];
});
vector<int> dp(n + 10, 0);
for(int i = 1; i <= n; i ++){
int l = 0, r = n;
while(l < r){
int mid = l + r + 1 >> 1;
if(jobs[mid][1] <= jobs[i][0]) l = mid;
else r = mid - 1;
}
dp[i] = max(dp[i - 1], dp[l] + jobs[i][2]);
}
return dp[n];
}
};
三:最小点覆盖区间问题
1.描述
给定若干个区间,现在要求放点,使得每个区间的范围内都至少有一个点,试求解点数的最小值?
2.思路
我们不妨先考虑所有区间中右端点最左的那个区间。由于这个区间的右端点最左,它不可能被顺手覆盖,我们是一定要在这个区间的范围内放一个点。放在这个区间的任意位置都可以,那么如何放才能顺手覆盖尽可能多的区间?
显然把这个点放在这个区间的右端点才能尽可能覆盖其他区间。
以此类推,第二、第三 …… 所有的区间都应该如此考虑。
3.做法
(1)对所有区间进行排序,以区间的右端点为判断条件,右端点小的区间在前。
(2)记录当前点的位置 x,所有被这个点覆盖的区间都可以不管,只有当枚举到的区间的左端点大于 x 时,答案加一,更新 x 为这个区间的右端点。
4.例题
AC代码
class Solution {
public:
int findMinArrowShots(vector<vector<int>>& points) {
sort(points.begin(), points.end(), [](const auto& u, const auto& v){
return u[1] < v[1];
});
int n = points.size();
int ans = 1, x = points[0][1];
for(int i = 0; i < n; i ++){
if(points[i][0] > x){
ans ++;
x = points[i][1];
}
}
return ans;
}
};

浙公网安备 33010602011771号