区间移位
二分
看到最大化最小值或最小化最大值,就要想到二分答案。本题是二分搜索结果。
分析样例可以知道,答案可能存在0.5,1.5,2.5等。那么我们将所有数值扩大两倍,最后再除2就可以了。
接下来我们讨论应该采用什么贪心策略能够正确的判断当前枚举的数值行与不行。
- 区间乱序:假设有区间1~
10,2-4,来覆盖1~12的区间。遍历的每个区间要保证覆盖左侧未覆盖的点,第一个区间移动距离为0,第二个区间的移动距离为8。分析可知,正确的结果应为2,说明这种策略是不合理的。 - 按照区间左端点对区间从小到大进行排序:假设有区间1~ 10,2-4,来覆盖1~ 12的区间。排序后仍为1 ~ 10,2~4;分析可知这个也是不合理的。
- 按照区间右端点对区间从小到大进行排序:假设有区间1~ 10,2-4,来覆盖1~ 12的区间。排序后为2~
4,1~10。这种策略可以保证当前样例的正确性。从0开始更新覆盖的范围,每次使用一个可能的有边界尽量小的区间来更新覆盖围。以使右边界更大的区间剩余下来,尽量往右移动来覆盖更大的区域。注意:采用这种策略能够在一定程度上尽量保证结果的正确性,但是还会存在一定的特殊样例结果不对。因为这个题目的数据量比较小,所以我们可以采用多次遍历区间来解决,具体看代码。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int n, a, b;
struct node
{
int a, b;
};
vector<node> q;
bool cmp(node x, node y)
{
return x.b < y.b;
}
bool check(int m) //最大移动距离m,判断是否能覆盖整个区间;
{
vector<node> tmp(q); //建立一个q的复制tmp;
int k = 0; //最大能覆盖的区间右端点;
while(true)
{
bool flag = false;
for(int i = 0; i < tmp.size(); i++)
{
node now = tmp[i];
int ta = now.a;
int tb = now.b;
if(ta-m <= k && tb+m >= k) //k在区间[ta-x, tb+x]内,不在则m太小;
{
flag = true;
int len = tb - ta; //当前这个区间的长度;
if(ta+m >= k)
k = k + len; //k最大能多覆盖到k+len的范围;
//ta+x>=k,那么只需要让区间的左端点移动到k处,右端点此时在k+len处;
else
k = tb + m; //ta+x<k,k最大能多覆盖到该区间的右端点,再多走m的长度;
tmp.erase(tmp.begin()+i); //将选择的区间去掉,避免重复判断;
break;
}
}
if(k >= 20000 || !flag) break;
//如果找不到可以覆盖的区间,或者k已经达到20000了,就可以结束检验了;
}
return k >= 20000;
}
int main()
{
cin >> n;
for(int i = 0; i < n; i++)
{
cin >> a >> b;
a *= 2; //答案存在0.5,可以将所有东西扩大两倍,最后再除2就可以了;
b *= 2;
q.push_back({a, b});
}
sort(q.begin(), q.end(), cmp); //按右端点从大到小排序;
int l = 0, r = 20000; //因为扩大了2倍,枚举答案范围也大了2倍;
while(l <= r) //二分答案:枚举最大移动的距离,使最大移动距离最小;
{
int mid = (l+r)/2;
if(check(mid))
r = mid - 1;
else
l = mid + 1;
}
double ans = (r + 1) / 2.0;
cout << ans << endl;
return 0;
}