题目描述

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

输入

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

输出

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

样例输入

4
1 1
5 1
6 5
15 15

样例输出

32


题解

线段树优化建图+Tarjan+拓扑排序

看到题第一眼想到图论,然而边数爆炸难以承受,由于一个炸弹覆盖的是一个区间,因此想到使用线段树优化建图来解决。

建完图问的就是一个点最多能够遍历多少个点,直接Tarjan缩点+拓扑排序递推是无法统计的,因为状态会转移重复。

但是考虑到本题有一个特殊的性质:一个炸弹能够引爆的所有炸弹(包括连锁反应)一定也是一段区间,对于区间只需要求出区间左右端点的位置即可。

因此对于每个点维护它的位置,要求的就是一个点能够经过的所有点的位置最大&最小值。由于最值是可以重复统计的,因此Tarjan缩点,然后建反图按拓扑序递推即可。最后直接使用二分查找找出一个炸弹覆盖的个数。

时间复杂度$O(n\log n)$,貌似这不是正解,但是复杂度是对的,给代码1K-的dalao跪了。。。

#include <queue>
#include <cstdio>
#include <algorithm>
#define N 500010
#define lson l , mid , x << 1
#define rson mid + 1 , r , x << 1 | 1
using namespace std;
queue<int> q;
long long a[N] , v[N] , mn[N * 4] , mx[N * 4] , vmin[N * 4] , vmax[N * 4];
int n , pos[N] , head[N * 4] , to[N * 40] , next[N * 40] , cnt;
int deep[N * 4] , low[N * 4] , tot , ins[N * 4] , sta[N * 4] , top , bl[N * 4] , num;
int hh[N * 4] , tt[N * 40] , nn[N * 40] , cc , rd[N * 4];
inline void add(int x , int y)
{
to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
}
void build(int l , int r , int x)
{
if(l == r)
{
pos[l] = x;
return;
}
int mid = (l + r) >> 1;
mn[x] = 1ll << 62 , mx[x] = -1ll << 62;
build(lson) , build(rson);
add(x , x << 1) , add(x , x << 1 | 1);
}
void update(int b , int e , int p , int l , int r , int x)
{
if(b <= l && r <= e)
{
add(p , x);
return;
}
int mid = (l + r) >> 1;
if(b <= mid) update(b , e , p , lson);
if(e > mid) update(b , e , p , rson);
}
void tarjan(int x)
{
int i;
deep[x] = low[x] = ++tot , ins[x] = 1 , sta[++top] = x;
for(i = head[x] ; i ; i = next[i])
{
if(!deep[to[i]]) tarjan(to[i]) , low[x] = min(low[x] , low[to[i]]);
else if(ins[to[i]]) low[x] = min(low[x] , deep[to[i]]);
}
if(deep[x] == low[x])
{
int t;
num ++ , vmin[num] = 1ll << 62 , vmax[num] = -1ll << 62;
do
{
t = sta[top -- ] , ins[t] = 0 , bl[t] = num;
vmin[num] = min(vmin[num] , mn[t]) , vmax[num] = max(vmax[num] , mx[t]);
}while(t != x);
}
}
int main()
{
int n , i , x;
long long ans = 0;
scanf("%d" , &n);
build(1 , n , 1);
for(i = 1 ; i <= n ; i ++ ) scanf("%lld%lld" , &a[i] , &v[i]) , mn[pos[i]] = mx[pos[i]] = a[i];
a[n + 1] = 1ll << 62;
for(i = 1 ; i <= n ; i ++ )
update(lower_bound(a + 1 , a + n + 2 , a[i] - v[i]) - a , upper_bound(a + 1 , a + n + 2 , a[i] + v[i]) - a - 1 , pos[i] , 1 , n , 1);
for(i = 1 ; i <= n * 4 ; i ++ )
if(!deep[i])
tarjan(i);
for(x = 1 ; x <= n * 4 ; x ++ )
for(i = head[x] ; i ; i = next[i])
if(bl[x] != bl[to[i]])
tt[++cc] = bl[x] , nn[cc] = hh[bl[to[i]]] , hh[bl[to[i]]] = cc , rd[bl[x]] ++ ;
for(i = 1 ; i <= num ; i ++ )
if(!rd[to[i]])
q.push(to[i]);
while(!q.empty())
{
x = q.front() , q.pop();
for(i = hh[x] ; i ; i = nn[i])
{
vmin[tt[i]] = min(vmin[tt[i]] , vmin[x]) , vmax[tt[i]] = max(vmax[tt[i]] , vmax[x]) , rd[tt[i]] -- ;
if(!rd[tt[i]]) q.push(tt[i]);
}
}
for(i = 1 ; i <= n ; i ++ )
ans = (ans + (long long)(upper_bound(a + 1 , a + n + 1 , vmax[bl[pos[i]]]) - lower_bound(a + 1 , a + n + 1 , vmin[bl[pos[i]]])) * i) % 1000000007;
printf("%lld\n" , ans);
return 0;
}

【bzoj5017】[Snoi2017]炸弹 线段树优化建图+Tarjan+拓扑排序的更多相关文章

  1. BZOJ5017 [SNOI2017]炸弹 - 线段树优化建图+Tarjan

    Solution 一个点向一个区间内的所有点连边, 可以用线段树优化建图来优化 : 前置技能传送门 然后就得到一个有向图, 一个联通块内的炸弹可以互相引爆, 所以进行缩点变成$DAG$ 然后拓扑排序. ...

  2. bzoj5017 [Snoi2017]炸弹 (线段树优化建图+)tarjan 缩点+拓扑排序

    题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=5017 题解 这个题目方法挺多的. 线段树优化建图 线段树优化建图的做法应该挺显然的,一个炸弹能 ...

  3. bzoj5017 炸弹 (线段树优化建图+tarjan+拓扑序dp)

    直接建图边数太多,用线段树优化一下 然后缩点,记下来每个点里有多少个炸弹 然后按拓扑序反向dp一下就行了 #include<bits/stdc++.h> #define pa pair&l ...

  4. 模拟赛T2 线段树优化建图+tarjan+拓扑排序

    然而这只是 70pts 的部分分,考场上没想到满分怎么做(现在也不会) code: #include <cstdio> #include <string> #include & ...

  5. [SNOI2017]炸弹[线段树优化建图]

    [SNOI2017]炸弹 线段树优化建图,然后跑一边tarjan把点全部缩起来,炸一次肯定是有连锁反应的所以整个连通块都一样-于是就可以发现有些是只有单向边的不能忘记更新,没了. #include & ...

  6. BZOJ5017 炸弹(线段树优化建图+Tarjan+拓扑)

    Description 在一条直线上有 N 个炸弹,每个炸弹的坐标是 Xi,爆炸半径是 Ri,当一个炸弹爆炸时,如果另一个炸弹所在位置 Xj 满足:  Xi−Ri≤Xj≤Xi+Ri,那么,该炸弹也会被 ...

  7. 『炸弹 线段树优化建图 Tarjan』

    炸弹(SNOI2017) Description 在一条直线上有 N 个炸弹,每个炸弹的坐标是 Xi,爆炸半径是 Ri,当一个炸弹爆炸 时,如果另一个炸弹所在位置 Xj 满足: Xi−Ri≤Xj≤Xi ...

  8. BZOJ5017 [Snoi2017]炸弹[线段树优化建边+scc缩点+DAG上DP/线性递推]

    方法一: 朴素思路:果断建图,每次二分出一个区间然后要向这个区间每个点连有向边,然后一个环的话是可以互相引爆的,缩点之后就是一个DAG,求每个点出发有多少可达点. 然后注意两个问题: 上述建边显然$n ...

  9. 炸弹:线段树优化建边+tarjan缩点+建反边+跑拓扑

    这道题我做了有半个月了...终于A了... 有图为证 一句话题解:二分LR线段树优化建边+tarjan缩点+建反边+跑拓扑统计答案 首先我们根据题意,判断出来要炸弹可以连着炸,就是这个炸弹能炸到的可以 ...

随机推荐

  1. 3.Mysql集群------Mycat分库分表

    前言: 分库分表,在本节里是水平切分,就是多个数据库里包含的表是一模一样的. 只是把字段散列的分到不同的库中. 实践: 1.修改schema.xml 这里是在同一台服务器上建立了4个数据库db1,db ...

  2. 永久免费开源的卫星地形图地图下载工具更新Somap2.13版本功能更新 更新时间2019年2月22日13:59:05

    一.下载地址 最新版本下载地址:SoMap2.13点击此处下载  二.系统自主开发特色功能展示 1.上百种地图随意下载 高德.百度.arcgis.谷歌.bing.海图.腾讯.Openstreet.天地 ...

  3. Inventory Update-freecodecamp算法题目

    Inventory Update 1.要求 依照一个存着新进货物的二维数组,更新存着现有库存(在 arr1 中)的二维数组. 如果货物已存在则更新数量 . 如果没有对应货物则把其加入到数组中,更新最新 ...

  4. python-读写文件的方式

    open(path, flag[, encoding][, errors]) path:要打开文件的路径 flag:打开方式 r 以只读的方式打开文件,文件的描述符放在文件的开头 rb 以二进制格式打 ...

  5. 多进程(multiprocessing module)

    一.多进程 1.1 多进程的概念 由于GIL的存在,python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程.Python提供了非常好 ...

  6. 多线程(threading module)

    一.线程与进程 线程定义:线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不 ...

  7. ajax状态值和状态码

    AJAX状态值是指,运行AJAX所经历过的几种状态,无论访问是否成功都将响应的步骤,可以理解成为AJAX运行步骤.如:正在发送,正在响应等,由AJAX对象与服务器交互时所得:使用“ajax.ready ...

  8. Python__关于列表的引用 以append操作为例

    对于列表这样的可变类型来说,对它操作是不会改变内存地址的. 若列表里面存的元素是整数这样的不可变类型,若修改这个元素那地址还是会改变,如: >>> a = [,,] >> ...

  9. 记 页面使用overflow-scroll在iOS上滑动卡顿的问题

    页面使用overflow-scroll在iOS上滑动卡顿的问题 因在做一个滑动的list列表,为某个div使用了overflow: scroll属性. 结果在手机上测试时,ios手机有明显的滑动卡顿问 ...

  10. scrapy进行分布式爬虫

    今天,参照崔庆才老师的爬虫实战课程,实践了一下分布式爬虫,并没有之前想象的那么神秘,其实非常的简单,相信你看过这篇文章后,不出一小时,便可以动手完成一个分布式爬虫! 1.分布式爬虫原理 首先我们来看一 ...