经典的区间三个贪心问题。再总结一下,以作复习。
选择不重叠区间
有 n 个区间 [li,ri],选择尽量多的区间,使得区间之间不重叠(端点可以重叠)。
将右端点从小到大排序(左端点可以随便排序),然后先选择右端点比较小的(不与之前选过的重叠)。
这样的贪心策略是正确的。
考虑如下一个例子。先选择右端点最小的 [1,4],然后右端点次大的 [2,5],[3,5] 重叠无法选择,然后是右端点最大的 [4,6] 可以选择。即最多可以选出两个区间。
假设一开始没有选择右端点最小的,选择了 [2,5], 那么 [1,2] 的部分就被浪费了,还额外占用了 [4,5] 的空间,所以一定从右端点小的开始选更划算。
区间选点问题
有 n 个区间 [li,ri],选择尽可能少的点,使得每一个区间内都有点。
事实上,这个问题的答案和选择不重叠区间的答案是一样的。
为什么两个问题的答案相同?
考虑所有最大不重叠区间,至少要选最大不重叠区间数量的点。(这是答案的下界)
然后,我们证明选最大不重叠区间数量的点一定能覆盖所有区间。
如果一个不重叠区间内需要两个点才能把所有区间覆盖,则需要覆盖的两个区间一定是一个不重叠区间更优答案,与最大不重叠区间矛盾,所以一个不重叠区间内只要选一个点就够了。
还有一种解释是将右端点从小到大排序,然后选择区间的最后一个点。(因为选择前面的点一定没有后面的点划算)
比如在这个例子中,区间 [1,4] 选择点 4,然后 [2,5],[3,5] 已经被覆盖,跳过,区间 [4,6] 选择 6。所以至少要选两个点。可以发现,答案确实与最大不重叠区间相同。
区间覆盖问题
有 n 个区间 [li,ri],选择尽可能少的区间,覆盖线段 [s,t]。
- 先按照左端点从小到大排序。记录目前能到的最右端位置
now
(一开始为 0)。
- 从所有左端点比最右端位置小的区间中(即
a[i].l <= now
),选出右端点最大的,直到覆盖完。
- 如果在最后找不到一个左端点比最右端位置小的区间,且此时还没有到 t,则问题无解。
来看一道具体题目。
例题
LOJ#10002. 喷水装置
长 L 米,宽 W 米的草坪里装有 n 个浇灌喷头。每个喷头都装在草坪中心线上(离两边各 2W 米)。我们知道每个喷头的位置(离草坪中心线左端的距离),以及它能覆盖到的浇灌范围。请问:如果要同时浇灌整块草坪,最少需要打开多少个喷头?
首先,一个喷头的有效区域是一个矩形,可看作一个区间。具体来说,对于一个位置在 d,半径为 r 的喷头:
- r≤2W:此时该喷头无效。
- r>2W:此时该喷头的有效范围为
d−r2−(2W)2,d+r2−(2W)2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include<bits/stdc++.h> using namespace std;
const int N = 1000;
int n,L,W;
void solve(){ vector<pair<double,double>> range;
scanf("%d%d%d",&n,&L,&W); for(int i=0;i<n;i++){ int a,r; scanf("%d%d",&a,&r); if((double)r<=W/2)continue; double h = sqrt(pow(double(r),2)-pow(W/2.,2)); range.push_back({fmax(0.,a-h),a+h}); }
sort(range.begin(),range.end());
double now=0; int i=0; int ans = 0; while(now<L){ double to=-1; for(;range[i].first<=now&&i<range.size();i++){ to=max(to,range[i].second); } if(to==-1){ puts("-1"); return; } now = to; ans++; }
printf("%d\n",ans); return; }
int main(){ int T;scanf("%d",&T); while(T--)solve(); }
|
核心逻辑在第 22~37 行。