区间移位
问题描述
- 数轴上有
n
个闭区间 D 1 , ⋯ , D n D_1,\cdots,D_n D1,⋯,Dn。其中区间 D i D_i Di用一对整数[ a i a_i ai, b i b_i bi]来描述,满足 a i a_i ai < b i b_i bi。已知这些区间的长度之和至少有10000。所以,通过适当的移动这些区间,你总可以使得他们的“并”覆盖[0, 10000]——也就是说[0, 10000]这个区间内的每一个点都落于至少一个区间内。 - 希望你找一个移动方法,使得位移差最大的那个区间的位移量最小。具体来说,假设你将 D i D_i Di移动到[ a i + c i a_i+c_i ai+ci, b i + c i b_i+c_i bi+ci]这个位置。你希望使得 m a x ∣ c i ∣ max|c_i| max∣ci∣最小。
输入格式
- 输入的第一行包含一个整数
n
,表示区间的数量。 - 接下来有
n
行,每行2个整数 a i a_i ai, b i b_i bi,以一个空格分开,表示区间[ a i a_i ai, b i b_i bi]。保证区间的长度之和至少是10000。
输出格式
- 输出一个数,表示答案。如果答案是整数,只输出整数部分。如果答案不是整数,输出时四舍五入保留一位小数。
样例输入一
2
10 5010
4980 9980
样例输出一
20
样例说明一
- 第一个区间往左移动10;第二个区间往右移动20。
样例输入二
4
0 4000
3000 5000
5001 8000
7000 10000
样例输出二
0.5
样例说明
- 第2个区间往右移0.5;第3个区间往左移0.5即可。
数据规模和约定
- 对于30%的评测用例,1 ≤
n
≤ 10; - 对于100%的评测用例,1 ≤
n
≤ 10000,0 ≤ a i a_i ai < b i b_i bi≤ 10000。
思路
-
首先是==二分思想==
-
然后比较容易想到要对输入的
n
区间进行排序,但怎么排序?- 我首先想到的是对这
n
个区间的左端点进行从大到小的排序,但这样可能会造成比较大的区间浪费和较多的移动,因此选择根据右端点进行排序,这样移动的较少就可以覆盖的更多。 - 如下图所示。若按左端点排序,则区间
i
应该在区间j
的前面(即先放区间i
,再放区间j
),这时区间j
可能就放不了了或需要更大的移动,造成区间浪费。但是如果按右端点排序,区间j
在区间i
的前面(即先放区间j
,再放区间i
)。(这里还是解释不清楚,姑且这么理解吧)
- 我首先想到的是对这
-
其次是在二分出
mid
后,Judge
函数的书写(即判断当前mid
是否满足题意,满足的时候扩大,不满足的时候缩小)- 如何判断当前
mid
值是否满足要求?- 程序中使用
max_range
变量维护当前可以覆盖的最远坐标。 - 假设现在处理到第
j
个区间 [ a j , b j ] [a_j,b_j] [aj,bj]。若a[j] - mid <= max_range && b[j] + mid >= max_range
,则该区间可以在移动mid
单位的限制下继续扩大覆盖范围,继续查找下一个区间(这里还要注意更新max_range
的取值,具体细节见下一条);反之,则应当放宽当前的mid
限制(即退出Judge
函数)。 - 若
a[j] + mid >= max_range
,则max_range = b[j] - a[j] + max_range
;否则,max_range = b[j] + mid
。
- 程序中使用
- 还要注意不能改变range数组的值,要备份!!!其中的
for
循环也要注意!在剩余区间集中找到一个满足条件的即可,不一定非要某一个区间。
- 如何判断当前
-
最后是一些小细节
- 正常二分题中,最后的答案是不可能为小数的,但这道题最后的答案可能为小数(小数只能为0.5,1.5,2.5, ⋯ \cdots ⋯)。大多数博客都选择将原来的区间长度10000乘以2来规避小数情况(读入的区间信息也要乘以2)。
C++代码
#include<stdio.h>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
#define MAXN 20000
struct node{
int a;
int b;
};
vector<node> range;
// 用于判断mid的函数
bool Judge(int mid);
// 用于C++中的sort函数
bool cmp(node x, node y);
int main(){
// 输入数据
int n = 0, i = 0;
int a = 0, b = 0;
scanf("%d", &n);
for(i = 0; i < n; i++){
scanf("%d %d", &a, &b);
// 规避小数
a = a * 2;
b = b * 2;
range.push_back({a, b});
}
// 根据区间的右端点进行排序
sort(range.begin(), range.end(), cmp);
// 二分查找
int left = 0, right = MAXN;
double result = 0;
while(left <= right){
int mid = (left + right) / 2;
if(Judge(mid)){
right = mid - 1;
result = mid;
} else {
left = mid + 1;
}
}
// 输出结果
cout << result/2.0 << endl;
return 0;
}
bool Judge(int mid){
int i = 0, max_range = 0;
bool judgement;
vector<node> temp(range);
while(true)
{
judgement = false;
// 注意:在剩余区间中找到一个就可以
// 找到之后还要将其删除
for(i=0;i<temp.size();i++)
{
node temp_node = temp[i];
if((temp_node.a - mid) <= max_range && (temp_node.b + mid) >= max_range){
judgement = true;
if(temp_node.a + mid >= max_range)
max_range = max_range + temp_node.b - temp_node.a;
else
max_range = temp_node.b + mid;
temp.erase(temp.begin()+i);
break;
}
}
if(!judgement || max_range >= MAXN)
break;
}
return max_range >= MAXN;
}
bool cmp(node x, node y){
return x.b < y.b;
}
参考博客
}
}
if(!judgement || max_range >= MAXN)
break;
}
return max_range >= MAXN;
}
bool cmp(node x, node y){
return x.b < y.b;
}
## 参考博客
[【蓝桥杯有点意思】区间移位 二分答案-CSDN博客](https://blog.csdn.net/kiko_caoyue/article/details/87922292#:~:text=对于每一个x (也就是mid),可知所有的区间移动距离都小于等于x,那么对于一个区间 [a%2Cb]%2C其移动范围也就是 [a-x%2Cb%2Bx]之间。 然后我们的k从0开始,在区间内找其覆盖,如果k在 [a-x%2Cb%2Bx]之间,那么这个区间就可以把k覆盖,然后我们进行如下讨论: int len,k %3D tb%2Bx%3B 1 2 3 ta是目前的区间的左端点,tb是目前区间的右端点。 如果ta%2Bx>%3Dk,那么我们只需要让区间的左端点移动到k处,右端点此时在k%2Blen处。)