題目描述
數軸上有 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;
}