一、题意
给你n条线段(线段端点为整数,n<=100000),输入保证线段间没有包含关系,且保证有解。
现在要求将每条线段截取长度为1的一小段,使得截取后的所有线段之间的空隙数目最少。
输出空隙的最少数目。
二、解析
一开始用了一种比较复杂的方法:
将所有线段按右端点排序后,每个线段尽量靠左边选取长度为1的片段,并维护一个remain值,表示该长度为1的片段右侧的空余长度(也就是可以动态右移多少长度);还要维护一个cur值,表示当前已经选取的1片段的最右端位置。然后每次当发现当前线段与上一线段的1片段(cur值)之间有空隙时,首先检查remain能否填充这个空隙,若能,则将该空隙填充;若不能则说明这个空隙必须存在,即ans++。每次判定完一个片段需要更新remain值、cur值。
这算是一种比较完备的思路,具体见后面代码。后来在网上发现一种简单一些的思路:
由于题目中已经保证有解,因此其实根本不需要判定是否放得下,只需要执行贪心策略即可:即还是按照线段的右端点排序,然后第一个线段选取最靠右边的长度为1的片段(贪心),然后后续线段就紧靠着选取即可。若发现无法紧靠,即必然有空隙,则ans++;否则就一定能放得下。这种方法就只需要维护一个cur值表示当先选取的1片段得最右端即可。
唯一要细节处理得就是,若当前线段与已选取1片段得最右端重合,则不需要改变cur值,因为题目保证有解,所以这里隐含得操作实际上是,将前面的所有片段左移1个单位,然后再将当前1片段放在这里。
三、代码
- 比较完备的代码
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 5, INF = 1 << 30;
struct section{
int l, r;
bool operator < (const section& rhs) const {
return r < rhs.r || r == rhs.r && l < rhs.l;
}
} a[maxn];
int kase, n;
int main(){
cin >> kase;
while(kase --){
cin >> n;
for(int i = 0; i < n; i ++) cin >> a[i].l >> a[i].r;
sort(a, a + n);
int ans = 0, remain = INF, cur = a[0].l;
for(int i = 0; i < n; i ++){
if(cur + 1 > a[i].r) ans ++, cur = a[i].l + 1;
else if(cur < a[i].l){
if(a[i].l - cur <= remain) remain -= a[i].l - cur;
else ans ++, remain = INF;
cur = a[i].l + 1;
}
else cur ++;
remain = min(remain, a[i].r - cur);
}
cout << ans << endl;
}
}- 比较取巧的代码
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 100000 + 5;
struct section {
int l, r;
bool operator < (const section& rhs) const {
return r < rhs.r || r == rhs.r && l < rhs.l;
}
} a[maxn];
int kase, n;
int main() {
cin >> kase;
while(kase --) {
cin >> n;
for(int i = 0; i < n; i ++) cin >> a[i].l >> a[i].r;
sort(a, a + n);
int cur = a[0].r, ans = 0; // 贪心:第一个片段靠最右边放,之后的依次紧靠
for(int i = 1; i < n; i ++) {
if(a[i].l > cur) ans ++, cur = a[i].r;
else if(a[i].r == cur) continue;
else cur ++; // 由于线段端点为整数,所以只要右端不相等,就至少大1,一定足够放
}
cout << ans << endl;
}
}四、归纳
- 要仔细看清楚题目,说不定题目没有你想的那么复杂....
- 大多数时候,题目的输入全为整数值,这代表“偏移量”等要么为0,要么为1,这可能是简化题意的关键

京公网安备 11010502036488号