洛谷 P3132 [USACO16JAN]愤怒的奶牛Angry Cows 【二分】


传送门:洛谷 P3132


愤怒的奶牛Angry Cows

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 &lt; l [ m i d ] l[mid]+1&lt;l[mid] l[mid]+1<l[mid]
l [ i ] &lt; a [ i ] − a [ m i d − 1 ] l[i]&lt;a[i]-a[mid-1] l[i]<a[i]a[mid1](因为如果大于那么就应该是第i堆炸到mid-1)

又有: l [ m i d ] &lt; l [ i ] l[mid]&lt;l[i] l[mid]<l[i](因为l是单调的)
所以就要 l [ m i d − 1 ] + 1 &lt; a [ i ] − a [ m i d − 1 ] l[mid-1]+1&lt;a[i]-a[mid-1] l[mid1]+1<a[i]a[mid1]

因为 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;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值