P5025 [SNOI2017]炸弹

image

[SNOI2017]炸弹

题目描述

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

答案对 109+7 取模

输入格式

第一行,一个数字 n ,表示炸弹个数。
2n+1 行,每行两个整数,表示 xiri,保证 xi 严格递增。

输出格式

一个数字,表示 i=1ni× 炸弹 i 能引爆的炸弹个数。

样例 #1

样例输入 #1

4
1 1
5 1
6 5
15 15

样例输出 #1

32

提示

【数据范围】
对于 20% 的数据: n100

对于 50% 的数据: n1000

对于 80% 的数据: n100000

对于 100% 的数据: 1n5000001018xi10180ri2×1018

解析

很容易想到将一个炸弹和他能够引爆的炸弹连边建图,但n过大,这样就是一种暴力的做法。采用线段树优化建图的方式,这种方式适用于点向区间连边的问题,可以优化到nlogn。在本题中一个炸弹可以引爆一定范围的炸弹,而数据中x又是保证升序的,所以就用线段树连边。用id[]记录n个炸弹在线段树中的编号,连边和跑tarjan缩点时都用线段树中的节点编号。缩好点后再重新建图,对于每个连通块维护他所能引爆的最大范围(注意线段树中的节点也要维护该信息),对于u连通块,跑dfs找到他能到达的所有连通块,更新这个范围。
最后直接计算答案即可。

代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define mid ((l + r) >> 1)
#define ls k << 1
#define rs k << 1 | 1
const int N = 5e5 + 10, M = 2e6 + 10, mod = 1e9 + 7;
struct node {
int l, r;
}a[M];
ll x[N], r[N];
int dfn[M], low[M], bel[M];
int id[M], Left[M], Right[M];//id[i]表示i号炸弹在线段树中的编号
int n, nd, cnt, idx, ans;
stack<int> s;
vector<int> e[M], G[M];
bool vis[M];
void build(int k, int l, int r) {
a[k] = {l, r};
nd = max(nd, k);//线段树中最大编号
if (l == r) {id[l] = k; return ;}
build(ls, l, mid); build(rs, mid + 1, r);
e[k].push_back(ls); e[k].push_back(rs);
//k向自己的左右儿子连边
}
void link(int k, int l, int r, int L, int R, int v) {
if (L <= l && R >= r) {
if (k == v) return ;//判自环
e[v].push_back(k);
return ;
}
if (L <= mid) link(ls, l, mid, L, R, v);
if (R > mid) link(rs, mid + 1, r, L, R, v);
}
void tarjan(int x) {
dfn[x] = low[x] = ++ cnt;
s.push(x), vis[x] = 1;
for (int i = 0; i < e[x].size(); i ++) {
int y = e[x][i];
if (!dfn[y]) {
tarjan(y);
low[x] = min(low[x], low[y]);
}
else if (vis[y]) low[x] = min(low[x], dfn[y]);
}
if (dfn[x] == low[x]) {
idx ++;
int v;
do {
v = s.top(); s.pop();
vis[v] = 0;
bel[v] = idx;
Left[idx] = min(Left[idx], a[v].l);
Right[idx] = max(Right[idx], a[v].r);
//更新这个强连通分量中能到的左右端点
} while (v != x);
}
}
void dfs(int u) {
vis[u] = 1;
for (int i = 0; i < G[u].size(); i ++) {
int v = G[u][i];
if (vis[v]) {
Left[u] = min(Left[u], Left[v]);
Right[u] = max(Right[u], Right[v]);
continue;
}
dfs(v);
Left[u] = min(Left[u], Left[v]);
Right[u] = max(Right[u], Right[v]);
}
}
int query(int x) {
int u = bel[id[x]];
return Right[u] - Left[u] + 1;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i ++) scanf("%lld %lld", &x[i], &r[i]);
memset(Left, 0x3f, sizeof Left);
build(1, 1, n);
for (int i = 1; i <= n; i ++) {
int L = lower_bound(x + 1, x + n + 1, x[i] - r[i]) - x;
int R = upper_bound(x + 1, x + n + 1, x[i] + r[i]) - x - 1;
link(1, 1, n, L, R, id[i]);
a[id[i]] = {L, R};
}
tarjan(1);
for (int i = 1; i <= nd; i ++) {
for (int j = 0; j < e[i].size(); j ++) {
int u = e[i][j];
if (bel[u] == bel[i]) continue;
G[bel[i]].push_back(bel[u]);
}
}
for (int i = 1; i <= idx; i ++) {//去掉重复的边
sort(G[i].begin(), G[i].end());
unique(G[i].begin(), G[i].end());
}
memset(vis, 0, sizeof vis);
for (int i = 1; i <= idx; i ++)
if (!vis[i]) dfs(i);
for (int i = 1; i <= n; i ++)
ans = (ans + 1ll * query(i) * i) % mod;
printf("%d", ans);
return 0;
}

image



如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
posted @   YHXo  阅读(76)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示