传送门:洛谷 P3132
Problem Description
奶牛Bessie设计了一个游戏:“愤怒的奶牛”。游戏的原型是:有一些可爆炸的草堆分布在一条数轴的某些坐标上,玩家用弹弓把一头奶牛发射到数轴上。奶牛砸到数轴上的冲击波会引发附近的草堆爆炸,而被引爆的草堆可能会引爆其他草堆。游戏的目标是玩家用一只奶牛炸掉所有的草堆。
有N个草堆在数轴的不同位置,坐标为x1,x2,….,xn。如果玩家以能量R把奶牛发射到坐标x,就会引爆半径R及以内的的草堆,即坐标范围[x−R,x+R]的草堆都会燃爆,每个被奶牛引爆的草堆又会2次引爆半径R-1及以内的的草堆,2次引爆的草堆又会3次引爆半径R-2及以内的的草堆…直到一次引爆后没有其他草堆被波及或半径为0。
现在只有1头奶牛,能量为R,请计算如果要引爆所有的草堆,最小的R是多少?
Input
第一行:1个整数N(2≤N≤50,000)。
下面有N行,每行一个整数:x1,x2,…,xn,范围在[0…1,000,000,000]。
Output
输出最小的能量R,保留1位小数。
感谢wenyu0909和世界第一弱的翻译。
Sample Input
5
8
10
3
11
1
Sample Output
3.0
思路:
看完题目我就想二分半径,但是奈何太菜,知道应该二分但是不会Orz 。然后看了大佬的思路,看了半天,终于看懂了,自己敲又敲了半天Orz 。
记 l [ i ] l[i] l[i] 表示以i为圆心爆炸向左可以覆盖前 i-1 个点的最小半径; r [ i ] r[i] r[i] 表示以i为圆心爆炸向右覆盖至第n个点的最小半径。
枚举i,第i个草堆为这次爆炸的左边界,再在左边界到右边界的草堆中枚举j,如果l[i]+1<=r并且r[i]+1<=r,则说明这个方案可行。
我个人认为比较难理解的是二分预处理 l , r l,r l,r 里的
if(l[mid-1]+1<a[i]-a[mid-1]) low=mid;
这句代码。
我是怎么理解的
l
[
i
]
l[i]
l[i]是我们要求的,mid 是我们假设的爆炸半径炸到mid 就可以覆盖前i-1个,
那么有两个表达式是可以确定的:
l
[
m
i
d
]
+
1
<
l
[
m
i
d
]
l[mid]+1<l[mid]
l[mid]+1<l[mid]
l
[
i
]
<
a
[
i
]
−
a
[
m
i
d
−
1
]
l[i]<a[i]-a[mid-1]
l[i]<a[i]−a[mid−1](因为如果大于那么就应该是第i堆炸到mid-1)
又有:
l
[
m
i
d
]
<
l
[
i
]
l[mid]<l[i]
l[mid]<l[i](因为l是单调的)
所以就要
l
[
m
i
d
−
1
]
+
1
<
a
[
i
]
−
a
[
m
i
d
−
1
]
l[mid-1]+1<a[i]-a[mid-1]
l[mid−1]+1<a[i]−a[mid−1]
因为
l
[
i
]
l[i]
l[i]记录的是最小半径,所以满足条件的话,就应该往mid的右边接着找。
求
r
[
i
]
r[i]
r[i]的时候也是同样的道理。
其他的都好理解,看上面链接就行。
本来是想刷tarjan的题目的,在洛谷上搜tarjan搜到这题,所以还是很好奇用tarjan怎么做 = =
AC代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const int N=550000;
int n;
double l[N],r[N];
double a[N];
void pre()
{
l[1]=0;
for(int i=2;i<=n;i++)
{
l[i]=inf;
int low=1,high=i;
while(low+1<high)
{
int mid=(low+high)/2;
if(l[mid-1]+1<a[i]-a[mid-1]) low=mid;
else high=mid;
}
l[i]=min(max(a[i]-a[low-1],l[low-1]+1),max(a[i]-a[high-1],l[high-1]+1));
}
r[n]=0;
for(int i=n-1;i>=1;i--)
{
r[i]=inf;
int low=i,high=n;
while(low+1<high)
{
int mid=(low+high)/2;
if(r[mid+1]+1<a[mid+1]-a[i]) high=mid;
else low=mid;
}
r[i]=min(max(a[low+1]-a[i],r[low+1]+1),max(a[high+1]-a[i],r[high+1]+1));
}
}
bool ifok(double x)
{
for(int i=n;i>=1;i--)
{
if(l[i]+1<=x)
{
for(int j=1;j<=n&&a[j]<=a[i]+2*x;j++)
{
if(r[j]+1<=x) return true;
}
break;
}
}
return false;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%lf",&a[i]);
}
sort(a+1,a+1+n);
pre();
double low=1,high=a[n];
while(high-low>0.01)
{
double mid=(low+high)/2;
if(ifok(mid)) high=mid;
else low=mid;
}
printf("%.1f\n",low);
return 0;
}