bzoj5017 [Snoi2017]炸弹 O(n)递推

5017: [Snoi2017]炸弹

Time Limit: 30 Sec   Memory Limit: 512 MB
Submit: 320   Solved: 118
[ Submit][ Status][ Discuss]

Description

在一条直线上有 N 个炸弹,每个炸弹的坐标是 Xi,爆炸半径是 Ri,当一个炸弹爆炸时,如果另一个炸弹所在位置 Xj 满足: 
Xi−Ri≤Xj≤Xi+Ri,那么,该炸弹也会被引爆。 
现在,请你帮忙计算一下,先把第 i 个炸弹引爆,将引爆多少个炸弹呢? 

Input

第一行,一个数字 N,表示炸弹个数。 
第 2∼N+1行,每行 2 个数字,表示 Xi,Ri,保证 Xi 严格递增。 
N≤500000
−10^18≤Xi≤10^18
0≤Ri≤2×10^18

Output

一个数字,表示Sigma(i*炸弹i能引爆的炸弹个数),1<=i<=N mod10^9+7。 

Sample Input

4
1 1
5 1
6 5
15 15

Sample Output

32


HINT

Source

[ Submit][ Status][ Discuss]


HOME Back

去年省选题啊,要是我去年省选肯定不会做。

网上的题解都是线段树优化建图+tarjan缩点+topo排序,这样做不仅复杂度高,而且代码量大,我还是讲一下当时的正解吧(雾

我们定义l[i]为炸弹i经过连锁反应之后能炸到的最靠左的炸弹是哪个,r[i]对称同理,R[i]为炸弹i的爆炸半径。

初始化l[i]=r[i]=i;

然后我们先求出l[i],具体求法为:

如果x[i]-x[l[i]-1]<=R[i]的话,左边那个炸弹是可以被影响到的,所以我们用l[l[i]-1]更新l[i],之后我们还需要更新R[i],因为左边的炸弹能够连锁反应,扩大炸弹i的实际爆炸范围,但是我们已经处理了左边,不需要R[i]的扩大对于左边的贡献了,所以我们只需要计算对右边的贡献,炸弹i通过引爆左边的炸弹,左边的炸弹又能影响到炸弹i右边的炸弹,而且还能使得R[i]变大,很明显R[l[i]]是R[i]的至少2倍才可行,因为我们只需要计算对右边的贡献,所以需要减掉i和l[i]之间的距离,具体见代码。

之后有了没个炸弹对右边的影响范围,我们求r[i]简直是易如反掌:

考虑从右向左求,如果x[r[i]+1]-x[i]<=新的R[i]的话,可以产生贡献,同理更新,同时我们要用l[r[i]]更新l[i],因为可能更靠左,这个时候的l都是已经求好的,所以不用在线更新。

之后统计答案即可。

复杂度:这是一个比较玄学的东西,但是大概这样:我们求解一个l[]或者r[]的时候,用来递推他的l[i]-1/r[i]+1已经是以前递推出来的极限值了,更新之后再次比较一定不会满足while循环的条件,所以while循环只会执行一次,这样时间复杂度就是O(n),我在loj上进行了测试,结果就是n-1;

代码极其好写:

#include 
using namespace std;
long long n,x[500005],R[500005],l[500005],r[500005],A;
main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%lld%lld",&x[i],&R[i]),l[i]=r[i]=i;
    for(int i=1;i<=n;i++) 
        while(l[i]>1&&x[i]-x[l[i]-1]<=R[i])
            l[i]=l[l[i]-1],R[i]=max(R[i],R[l[i]]-(x[i]-x[l[i]]));
    for(int i=n;i>=1;i--) 
        while(r[i]

你可能感兴趣的:(数据结构,思维)