區間問題匯總
區間合並
Description
給定 n n n 個區間 [ l i , r i ] [ l i , r i ] [l_i,r_i][l_i,r_i] [li,ri][li,ri],要求合並所有有交集的區間。
注意如果在端點處相交,也算有交集。
輸出合並完成后的區間個數。
例如:$[1,3][1,3] 和 和 和 [2,6][2,6] 可 以 合 並 為 一 個 區 間 可以合並為一個區間 可以合並為一個區間 [1,6][1,6]$。
Input
第一行包含整數 n n n。
接下來 n n n 行,每行包含兩個整數 l l l 和 r r r。
5
1 2
2 4
5 6
7 8
7 9
Output
共一行,包含一個整數,表示合並區間完成后的區間個數。
3
思路
- 使用
pair<int, int>
類型的vector
存儲所有集合的左右端點 - 將所有集合按左端點排序
- 創建
vector
用於存儲合並后的集合 - 若當前集合的左端點大於當前合並集合的右端點,則二者不能合並。
- 若不是第一個區間:將上一個合並后的區間放入vector,並更新 l , r l, r l,r為當前值
- 若是第一個區間:只更新 l , r l, r l,r為當前值
- 若當前集合的左端點小於等於當前合並集合的右端點,則二者可以合並。
- 取當前集合和當前合並集合的最大值,作為當前合並集合的最大值。
代碼
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
vector<pii> v;
int n;
void merge(vector<pii> &v){
vector<pii> opt;
int st = -1e9, ed = -1e9;
for(auto item : v){
if(ed < item.first){
if(st != -1e9) opt.push_back({st, ed});
st = item.first, ed = item.second;
}
else{
ed = max(ed, item.second);
}
}
if(st != -1e9) opt.push_back({st, ed});
v = opt;
}
int main(){
cin >> n;
for(int i = 0; i < n; i++){
int l, r;
cin >> l >> r;
v.push_back({l, r});
}
sort(v.begin(), v.end());
merge(v);
cout << v.size();
return 0;
}
區間選點
Description
給定 N N N 個閉區間 [ a i , b i ] [ a i , b i ] [a_i,b_i][a_i,b_i] [ai,bi][ai,bi],請你在數軸上選擇盡量少的點,使得每個區間內至少包含一個選出的點。
輸出選擇的點的最小數量。
位於區間端點上的點也算作區間內。
Input
第一行包含整數 n n n。
接下來 n n n 行,每行包含兩個整數 l l l 和 r r r。
3
-1 1
2 4
3 5
Output
2
思路
其實就是找到相互之間沒有任何交集的區間有幾個?
- 所有區間按右端點從小到大排序
- 遍歷所有區間:
- 初始區間為第一個區間
- 若該區間左端點小於選定區間右端點,
ans++
,更新該區間為選定區間
代碼
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int maxn = 0x3f3f3f3f;
int n, ans;
vector<pii> v;
int main(){
cin >> n;
for(int i = 0; i < n; i++){
int l, r; cin >> l >> r;
v.push_back({r, l});
}
sort(v.begin(), v.end());
int st = -maxn, ed = -maxn;
for(auto x : v){
if(x.second > ed){
ans ++;
ed = x.first;
}
}
cout << ans;
return 0;
}
區間分組
Description
給定 N N N 個閉區間 [ a i , b i ] [ a i , b i ] [a_i,b_i][a_i,b_i] [ai,bi][ai,bi],請你將這些區間分成若干組,使得每組內部的區間兩兩之間(包括端點)沒有交集,並使得組數盡可能小。
輸出最小組數。
Input
第一行包含整數 n n n。
接下來 n n n 行,每行包含兩個整數 l l l 和 r r r。
3
-1 1
2 4
3 5
Output
2
思路1
- 將所有區間按左端點從小到大排序
- 從前往后枚舉每個區間,判斷能否將其放到某個現有的組中,即是否存在當前區間的的左端點 L L L > 任意組中右端點的最小值的組
- 如果不存在這樣的組,則開新組,然后再將其放進組中
- 如果存在這樣的組,則將其放在符合條件的組中,並更新當前組的右端點的值
- 為了不用每次選擇組時都遍歷所有組,可以通過小根堆來維護所有組中的尾端
為什么不按右端點排序?(@小豆冰果Acwing)
比如,有n個人需要用教室,每個人占用教室的起始時間和終止時間是不一樣的。
- 如果想知道只有一間教室,能安排下的最多不沖突人數(不是所有的人都有機會,有的會被舍掉)是多少(區間選點和最大不相交問題),那么當然是最先結束的人排在前面,這樣后面的人才有更多機會。如果是按左端點排序,那么如過一個人0點開始用,那么肯定他排在最前面,但是如果他自己就占用了24小時,那么只能給他一個人用了,這樣就達不到最大的效果。所以按右端點排序。
- 如果想知道這些人都必須安排,沒有人被舍棄,至少需要多少個教室能安排下(區間分組問題)。那么肯定是按照開始時間排序,開始時間越早越優先。這樣每間教室都能得到最充分的利用。
代碼1
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int maxn = 0x3f3f3f3f;
int n, ans;
vector<pii> v, opt;
int main(){
cin >> n;
for(int i = 0; i < n; i++){
int l, r; cin >> l >> r;
v.push_back({l, r});
}
sort(v.begin(), v.end());
priority_queue<int, vector<int>, greater<int>> heap;
heap.push(maxn);
for(auto x : v){
if(x.first > heap.top()){
heap.pop();
heap.push(x.second);
}
else{
ans ++;
heap.push(x.second);
}
}
cout << ans;
return 0;
}
思路2
我們可以把所有開始時間和結束時間排序,遇到開始時間就把需要的教室加1,遇到結束時間就把需要的教室減1,在一系列需要的教室個數變化的過程中,峰值就是多同時進行的活動數,也是我們至少需要的教室數。
代碼2
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100100;
int n;
int b[2 * N], idx;
int main()
{
cin >> n;
for(int i = 0; i < n ; i ++)
{
int l, r;
scanf("%d %d", &l, &r);
b[idx ++] = l * 2;//標記左端點為偶數。
b[idx ++] = r * 2 + 1;// 標記右端點為奇數。
}
sort(b, b + idx);
int res = 1, t = 0;
for(int i = 0; i < idx ; i ++)
{
if(b[i] % 2 == 0) t ++;
else t --;
res = max(res, t);
}
cout << res << endl;
return 0;
}
作者:未來i
鏈接:https://www.acwing.com/solution/content/8902/
來源:AcWing
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
區間覆蓋
Description
給定 N N N 個閉區間 [ a i , b i ] [ a i , b i ] [a_i,b_i][a_i,b_i] [ai,bi][ai,bi] 以及一個線段區間 [ s , t ] [ s , t ] [s,t][s,t] [s,t][s,t],請你選擇盡量少的區間,將指定線段區間完全覆蓋。
輸出最少區間數,如果無法完全覆蓋則輸出 − 1 −1 −1。
Input
第一行包含兩個整數 s s s 和 t t t,表示給定線段區間的兩個端點。
第二行包含整數 N N N,表示給定區間數。
接下來 N N N 行,每行包含兩個整數 a i , b i a_i,b_i ai,bi,表示一個區間的兩個端點。
Output
輸出一個整數,表示所需最少區間數。
如果無解,則輸出 − 1 −1 −1。
思路
- 將所有區間按左端點從小到大進行排序
- 從前往后枚舉每個區間,在所有能覆蓋s的區間中,選擇右端點最大的區間,然后將s更新成右端點的最大值
代碼
#include<bits/stdc++.h>
using namespace std;
const int maxn = 0x3f3f3f3f;
typedef pair<int, int> pii;
int n, s, t, ans;
bool success;
vector<pii> v;
int main(){
cin >> s >> t;
cin >> n;
for(int i = 0; i < n; i++){
int l, r; cin >> l >> r;
v.push_back({l, r});
}
sort(v.begin(), v.end());
//雙指針思想
for(int i = 0; i < v.size(); i++){
int j = i,tmp = s;
while(v[j].first <= s && j < n){
tmp = max(tmp, v[j].second);
j++;
}
if(s == tmp || tmp < s){
ans = -1;
break;
}
ans++;
if(tmp >= t){
success = true;
break;
}
s = tmp;
i = j - 1;
}
if(success)cout<<ans;
else cout <<-1;
return 0;
}