蓝桥杯省赛 区间移位(二分+玄学贪心)
问题描述
数轴上有n个闭区间D1,…,Dn。其中区间Di用一对整数[ai, bi]来描述,满足ai < bi。已知这些区间的长度之和至少有10000。所以,通过适当的移动这些区间,你总可以使得他们的“并”覆盖[0, 10000]——也就是说[0, 10000]这个区间内的每一个点都落于至少一个区间内。
你希望找一个移动方法,使得位移差最大的那个区间的位移量最小。
具体来说,假设你将Di移动到[ai+ci, bi+ci]这个位置。你希望使得maxi |ci| 最小。
你希望找一个移动方法,使得位移差最大的那个区间的位移量最小。
具体来说,假设你将Di移动到[ai+ci, bi+ci]这个位置。你希望使得maxi |ci| 最小。
输入格式
输入的第一行包含一个整数n,表示区间的数量。
接下来有n行,每行2个整数ai, bi,以一个空格分开,表示区间[ai, bi]。保证区间的长度之和至少是10000。
接下来有n行,每行2个整数ai, bi,以一个空格分开,表示区间[ai, bi]。保证区间的长度之和至少是10000。
输出格式
输出一个数,表示答案。如果答案是整数,只输出整数部分。如果答案不是整数,输出时四舍五入保留一位小数。
样例输入
2
10 5010
4980 9980
10 5010
4980 9980
样例输出
20
样例说明
第一个区间往左移动10;第二个区间往右移动20。
样例输入
4
0 4000
3000 5000
5001 8000
7000 10000
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 ≤ ai < bi ≤ 10000。
对于100%的评测用例,1 ≤ n ≤ 10000,0 ≤ ai < bi ≤ 10000。
看到最大值最小很容易想到二分答案区间转化为判定,关键在于check函数怎么写。可以想到,需要让移动距离最大为mid的前提下把整个0~10000的区间铺满肯定是要贪心地移动,类似区间覆盖问题。不妨从左往右进行覆盖,设置一个变量pos表示从0~pos这一段已经被覆盖,需要从没有使用过的剩下的区间里选一段。假设目前在判断第i段区间,这时可以分为三种情况:s[i].l(即当前区间左端点)在pos左边,等于pos和在pos右边。处于pos两边时又需要看移动距离在mid内是否能衔接上pos,具体讨论可以看代码相关部分。
接下来就是最麻烦的地方,我也是看了不少博客才过了这题,也不知道自己理解的对不对。一开始我直接按区间左端点sort了一遍,然后只遍历一遍结构体数组,只得了50分还是六十分来着…后来按右端点sort,再把pos初始化为0,还是只遍历一遍结构体数组,竟然改到了90分…大佬给的解释是因为是从左边开始找的,所以按右端点排序,这样移动的较少就可以覆盖的更多。我觉得可依据这么一个例子:一个区间长为m,一个为n且m>n同时n这一段包含在m里面,如果按左端点排序的话会优先考虑使用m,似乎不太划算,相当于浪费长区间了;按右端点排序的话n不能满足衔接的要求才调用m,更合理一些。其次要注意的是,肯定不能只遍历一遍结构体数组,这样会有遗漏,因此用while循环嵌套一个for循环,最终复杂度是O(n^2logn)能过掉这题。还有一点就是题目中输出的答案可能有小数可能有整数,这其实很简单,因为区间端点全都是整数,所以mid要么是整数,要么就是0.5(两个区间同时凑出1),不可能有其他的比如0.3和0.7凑(整数端点移不出来),所以直接把端点*2,0~10000改成0~20000,输出的时候/2即可。
#include <bits/stdc++.h> using namespace std; int n; struct section { int l; int r; }s[10005]; bool cmp(section a,section b) { if(a.r!=b.r)return a.r<b.r; else return a.l<b.l; } bool check(int mid) { int i; int pos=0;//pos记录当前已经填好的区间的最末端 bool vis[10005]={0}; while(1) { bool find=false; for(i=1;i<=n;i++) { if(vis[i])continue; if(s[i].l>pos)//当前区间需要往左靠拢 { if(s[i].l-pos>mid)//这一段不能满足 { find=false; } else { pos=s[i].r-(s[i].l-pos); vis[i]=1; find=true; } } else if(s[i].l==pos) { pos=s[i].r; vis[i]=1; find=true; } else { if(pos-s[i].l<=mid) { pos=s[i].r+(pos-s[i].l); vis[i]=1; find=true; } else { if(s[i].r+mid>pos) { pos=s[i].r+mid; vis[i]=1; } find=true; } } } if(pos>=20000)return true; if(!find)break; } //这里别忘记改 return false; } int main() { int i; cin>>n; for(i=1;i<=n;i++) { scanf("%d%d",&s[i].l,&s[i].r); s[i].l*=2; s[i].r*=2; } sort(s+1,s+n+1,cmp); int left=0,right=20000,mid,ans=0; while(left<right) { mid=(left+right)/2; if(check(mid)) { right=mid; } else left=mid+1; } ans=left; if(ans%2==0) { cout<<ans/2; } else { double aans=ans/2.0; printf("%.1lf",aans); } return 0; }