P5025 [SNOI2017]炸弹

[SNOI2017]炸弹
题目描述
在一条直线上有 \(n\) 个炸弹,每个炸弹的坐标是 $ x_i $,爆炸半径是 $ r_i $,当一个炸弹爆炸时,如果另一个炸弹所在位置 $ x_j $ 满足:
$ |x_j-x_i| \le r_i $ ,那么,该炸弹也会被引爆。
现在,请你帮忙计算一下,先把第 \(i\) 个炸弹引爆,将引爆多少个炸弹呢?
答案对 \(10^9 + 7\) 取模
输入格式
第一行,一个数字 \(n\) ,表示炸弹个数。
第 \(2 \sim n+1\) 行,每行两个整数,表示 \(x_i\),\(r_i\),保证 \(x_i\) 严格递增。
输出格式
一个数字,表示 \(\sum \limits_{i=1}^n i\times\) 炸弹 \(i\) 能引爆的炸弹个数。
样例 #1
样例输入 #1
4
1 1
5 1
6 5
15 15
样例输出 #1
32
提示
【数据范围】
对于 \(20\%\) 的数据: \(n\leq 100\)。
对于 \(50\%\) 的数据: \(n\leq 1000\)。
对于 \(80\%\) 的数据: \(n\leq 100000\)。
对于 \(100\%\) 的数据: \(1\le n\leq 500000\),\(-10^{18}\leq x_{i}\leq 10^{18}\),\(0\leq r_{i}\leq 2\times 10^{18}\)。
解析
很容易想到将一个炸弹和他能够引爆的炸弹连边建图,但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;
}


浙公网安备 33010602011771号