【luogu P5025】炸弹(线段树优化建图)(Tarjan)


炸弹

题目链接:luogu P5025

题目大意

一个横轴让有一些炸弹,每个有爆炸范围,如果一个爆炸时另一个在的它的爆炸范围那那个也会爆炸。
然后问你对于每个炸弹引爆它会使多少炸弹爆炸。

思路

不难看出每个炸弹可以炸到那些我们可以通过左右分别二分 / STL 得到。
然后如果边数允许,我们可以直接连边表示炸了 \(i\) 就会跟着炸 \(j\),然后 Tarjan 缩点跑图得出答案。
但是边数是 \(n^2\) 的。

然后我们考虑它这些给一个区间的点连边如何优化,考虑用线段树。
线段树上一个包含 \(i\sim j\) 的点就代表 \(i\sim j\) 的所有点,朝他连边就是朝里面的所有点都连边。
然后我们在 Tarjan 重建图之后 dfs 一次把下面的范围传递给上面就可以用了。

然后因为要传上来所有 \(i\) 要给 \(i*2,i*2+1\) 连边。
然后直接搞就可以了。

代码

#include
#include
#include
#include
#define ll long long
#define mo 1000000007
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;

int n;
ll x[500001], r[500001];
int pl[500001];
vector  lnk[500001 << 2], e[500001 << 2];
ll ans;

struct XD_Tree {
	int ls[500001 << 2], rs[500001 << 2], MAXN;
	
	void build(int now, int l, int r) {
		ls[now] = l; rs[now] = r;
		MAXN = max(MAXN, now);
		if (l == r) {
			pl[l] = now; return ;
		}
		int mid = (l + r) >> 1;
		build(now << 1, l, mid); build(now << 1 | 1, mid + 1, r);
		lnk[now].push_back(now << 1); lnk[now].push_back(now << 1 | 1);
	}
	
	void connect(int now, int l, int r, int L, int R, int x) {
		if (L <= l && r <= R) {
			if (now == x) return ;
			lnk[x].push_back(now);
			return ;
		}
		int mid = (l + r) >> 1;
		if (L <= mid) connect(now << 1, l, mid, L, R, x);
		if (mid < R) connect(now << 1 | 1, mid + 1, r, L, R, x);
	}
}T;

int dfn[500001 << 2], low[500001 << 2], col[500001 << 2];
int lft[500001 << 2], rght[500001 << 2];
int sta[500001 << 2], tmp, tot;

void tarjan(int now) {
	dfn[now] = low[now] = ++tmp;
	sta[++sta[0]] = now;
	for (int i = 0; i < lnk[now].size(); i++) {
		int x = lnk[now][i];
		if (!dfn[x]) tarjan(x), low[now] = min(low[now], low[x]);
			else if (!col[x]) low[now] = min(low[now], dfn[x]);
	}
	if (low[now] == dfn[now]) {
		col[now] = ++tot;
		lft[tot] = T.ls[now]; rght[tot] = T.rs[now];
		while (sta[sta[0]] != now) {
			col[sta[sta[0]]] = tot;
			lft[tot] = min(lft[tot], T.ls[sta[sta[0]]]);
			rght[tot] = max(rght[tot], T.rs[sta[sta[0]]]);
			sta[0]--;
		}
		sta[0]--;
	}
}

bool in[500001 << 2];

void dfs(int now) {
	in[now] = 1;
	for (int i = 0; i < e[now].size(); i++) {
		int x = e[now][i];
		if (in[x]) {
			lft[now] = min(lft[now], lft[x]);
			rght[now] = max(rght[now], rght[x]);
		}
		else {
			dfs(x);
			lft[now] = min(lft[now], lft[x]);
			rght[now] = max(rght[now], rght[x]);
		}
	}
}

int query(int x) {
	x = col[pl[x]];
	return rght[x] - lft[x] + 1;
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%lld %lld", &x[i], &r[i]);
	}
	x[n + 1] = INF;
	
	T.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;
		T.connect(1, 1, n, L, R, pl[i]);
		T.ls[pl[i]] = L; T.rs[pl[i]] = R; 
	}
	tarjan(1);//线段树一定连通
	
	for (int i = 1; i <= T.MAXN; i++)
		for (int j = 0; j < lnk[i].size(); j++) {
			if (col[i] != col[lnk[i][j]]) {
				e[col[i]].push_back(col[lnk[i][j]]);
			}
		}
	for (int i = 1; i <= tot; i++) {//去重边
		sort(e[i].begin(), e[i].end());
		unique(e[i].begin(), e[i].end());
	}
	
	for (int i = 1; i <= tot; i++)
		if (!in[i]) dfs(i);
	
	for (int i = 1; i <= n; i++)
		(ans += 1ll * i * query(i) % mo) %= mo;
	printf("%lld", ans);
	
	return 0;
}