P5025 [SNOI2017]炸弹

image

[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;
}

image

posted @ 2022-10-19 14:50  YHXo  阅读(96)  评论(0)    收藏  举报