前言
蓝桥杯的题目真有意思。
题目比较实际化,所以这个题目的难度也就一般般,因为动手画画就能画出来。这个题是B组决赛最后一题吧。总感觉省赛的题目比较难
正文
这个题目的问法就很二分,我们直接二分起来。
check()中有贪心的成分,所以算法的正确性我确实证明不出来。然后他可以AC啊
直观上看,我们按右端点排序,然后移动到比较靠近没有覆盖的地方,就可以了。这样因为是有序的区间,所以移动的距离也是最小的。
我们怎么check()呢?
我先放上check()的代码,然后逐条解读:
bool check(int x)
{
int k = 0;
vector<node> tmp(reg);
while(true)
{
bool found = false;
for(int i=0;i<tmp.size();i++)
{
node now = tmp[i];
int ta = now.a;
int tb = now.b;
if(ta-x<=k && tb+x>=k)
{
found = true;
int len = tb-ta;
if(ta+x>=k) k += len;
else k = tb+x;
tmp.erase(tmp.begin()+i);
break;
}
}
if(!found || k>=maxn) break;
}
return k>=maxn;
}
对于每一个x(也就是mid),可知所有的区间移动距离都小于等于x,那么对于一个区间[a,b],其移动范围也就是[a-x,b+x]之间。
然后我们的k从0开始,在区间内找其覆盖,如果k在[a-x,b+x]之间,那么这个区间就可以把k覆盖,然后我们进行如下讨论:
int len = tb-ta;
if(ta+x>=k) k += len;
else k = tb+x;
ta是目前的区间的左端点,tb是目前区间的右端点。
如果ta+x>=k,那么我们只需要让区间的左端点移动到k处,右端点此时在k+len处。这样的变换的效果是最好的。(既减少了重复覆盖,移动距离也在x之内)
如果ta+x<k,那么我们最多只能移动x个距离,所以现在的k变成x+tb
如果我们找不到可以覆盖的区间,或者k已经达到maxn了,那么我们就可以认为这一轮的check()结束了。
选择了一个区间之后,把它删除,避免以后再次用到。
仔细观察样例我们发现,答案存在0.5,那么我们将所有东西扩大两倍,最后再除2就可以了。
下面是完整代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 20000;
int n;
struct node
{
int a;
int b;
};
vector<node> reg;
bool cmp(node x,node y)
{
return x.b<y.b;
}
bool check(int x)
{
int k = 0;
vector<node> tmp(reg);
while(true)
{
bool found = false;
for(int i=0;i<tmp.size();i++)
{
node now = tmp[i];
int ta = now.a;
int tb = now.b;
if(ta-x<=k && tb+x>=k)
{
found = true;
int len = tb-ta;
if(ta+x>=k) k += len;
else k = tb+x;
tmp.erase(tmp.begin()+i);
break;
}
}
if(!found || k>=maxn) break;
}
return k>=maxn;
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
a *= 2;
b *= 2;
reg.push_back({a,b});
}
sort(reg.begin(),reg.end(),cmp);
int l = 0,r = maxn;
double ans = 0;
while(l<=r)
{
int mid = (l+r)/2;
if(check(mid))
{
r = mid-1;
ans = mid;
}
else
{
l = mid+1;
}
}
ans/=2.0;
cout<<ans<<endl;
return 0;
}