题解-LibreOJ-2255炸弹

在一条直线上有 $n$ 个炸弹,每个炸弹给定其坐标和爆炸半径;求出引爆每个炸弹时,共有多少炸弹被连锁引爆。

$n\le 500000$

我感谢我自己

很巧妙的建模方法,是作者在学校 yy 出来的。

考虑朴素做法。将每个炸弹向其能引爆的炸弹连边,求出强连通分量后缩点,在 DAG 上乱搞得到答案。

然而这种解法的边数在 $n^2$ 级别。考虑优化。

注意到一个性质:每个炸弹能引爆的所有炸弹必然属于属于一段连续区间

在原炸弹序列上建一棵线段树,线段树上的所有父节点向儿子连边。对于一个节点向一段区间内节点的连边操作,我们可以将这些边连到区间的父节点上(好比线段树的区间操作)。边数被控制在 $n\log n$ 级别。

缩点并得到 DAG 后,发现不能简单地进行答案统计;因为会产生重复的状态转移。

观察到本题的一个特殊性质:被连锁反应引爆的范围一定仍然是一段连续区间。我们只需维护区间的最小值和最大值,并以此进行状态转移即可。

是否存在此类问题通解?

是否存在不用建图的解法?

ge(x) 表示大于等于 x 的最小坐标,le(x) 表示小于等于 x 的最大坐标

实现极为简单。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <bits/stdc++.h>
using namespace std;
#define lson(u) node[u].l
#define rson(u) node[u].r
#define ge(x) (lower_bound(a + 1, a + N + 1, x) - a)
#define le(x) (upper_bound(a + 1, a + N + 1, x) - a - 1)
typedef long long ll;

const int maxn = 1000005;
const int mod = 1000000007;
const int inf = 0x3f3f3f3f;

struct Node {
int l, r;
}node[maxn << 1];
int cnt;

int N, R, ans;
ll a[maxn], r[maxn];
int num[maxn], range[maxn][2];

vector<int> G[maxn], SCC[maxn];
int w[maxn], dfn[maxn], low[maxn], dfn_idx;
int scc[maxn], scc_cnt;
bool ins[maxn];
stack<int> s;

int f[maxn][2];
bool vis[maxn];

void build(int& u, int l, int r) {
u = ++cnt;
range[u][0] = l, range[u][1] = r;
if (l == r) {
num[l] = u;
w[u] = 1;
return;
}
int mid = (l + r) >> 1;
build(lson(u), l, mid), build(rson(u), mid + 1, r);
G[u].push_back(lson(u)), G[u].push_back(rson(u));
}

void add_edge_range(int u, int l, int r, int qu, int ql, int qr) {
if (ql <= l && r <= qr) {
G[qu].push_back(u);
return;
}
int mid = (l + r) >> 1;
if (ql <= mid) add_edge_range(lson(u), l, mid, qu, ql, qr);
if (mid < qr) add_edge_range(rson(u), mid + 1, r, qu, ql, qr);
}

void tarjan(int u) {
low[u] = dfn[u] = ++dfn_idx;
ins[u] = 1;
s.push(u);
for (vector<int>::iterator it = G[u].begin(); it != G[u].end(); it++) {
int v = *it;
if (dfn[v] == 0) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (ins[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if (low[u] == dfn[u]) {
scc_cnt++;
f[scc_cnt][0] = inf, f[scc_cnt][1] = -inf;
while (ins[u]) {
int t = s.top();
s.pop();
ins[t] = 0;
scc[t] = scc_cnt;
f[scc_cnt][0] = min(f[scc_cnt][0], range[t][0]);
f[scc_cnt][1] = max(f[scc_cnt][1], range[t][1]);
}
}
}

void dp(int u) {
if (vis[u]) return;
vis[u] = 1;
for (vector<int>::iterator it = SCC[u].begin(); it != SCC[u].end(); it++) {
int v = *it;
dp(v);
f[u][0] = min(f[u][0], f[v][0]);
f[u][1] = max(f[u][1], f[v][1]);
}
}

int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i)
scanf("%lld%lld", &a[i], &r[i]);
build(R, 1, N);
for (int i = 1; i <= N; ++i)
add_edge_range(R, 1, N, num[i], ge(a[i] - r[i]), le(a[i] + r[i]));
tarjan(1);
for (int u = 1; u <= N; ++u) {
for (vector<int>::iterator it = G[u].begin(); it != G[u].end(); it++) {
int v = *it;
if (scc[u] != scc[v])
SCC[scc[u]].push_back(scc[v]);
}
}
for (int i = 1; i <= scc_cnt; ++i)
dp(i);
for (int i = 1; i <= N; ++i) {
ans = (ans + 1ll * i * (f[scc[num[i]]][1] - f[scc[num[i]]][0] + 1)) % mod;
}
printf("%d", ans);
return 0;
}

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×