Solution
如果把每个炸弹看成点,将它向所有它能引爆的炸弹连有向边,那么图中同一个强连通分量就代表其中所有的炸弹可以相互引爆。这样我们对连边之后的图进行缩点,就能获得一个 DAG。
可以发现,缩点之后的每个点都代表了一段炸弹区间;而每个点最终能引爆的炸弹也形成一段区间。
因此,我们可以在 tarjan 的过程中记下每个点表示的左端点(Minl)和右端点(Maxr),然后反向建图进行拓扑排序,若 v->u,用 (Minl_v) 来更新 (Minl_u),用 (Maxr_v) 来更新 (Maxr_u),最终求得每个点实际能引爆的区间。然后我们就能知道每个炸弹能引爆的炸弹数,统计答案即可。
但是数据范围 (5 * 10^5),暴力建图的时间和空间复杂度都过高。考虑到是要向区间连边,可以使用线段树优化建图。如下图:
(线段树上每个节点向它的两个儿子连边)
如果我们要 2 节点向 [4,7] 连边,就可以采取如上方式,让 2->4,2->6,2->7。这个例子的优化不是很明显,但是一旦数据增大,可以将建图复杂度优化到 (log) 级别。
可以看出,线段树优化建图跟线段树的关系不大,只是采取了线段树的形式。
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#define LL long long
using namespace std;
const int N = 666666, mod = 1e9 + 7;
struct edge { LL nxt, to, f; } e[N << 3], f[N << 3];
LL n, id = 0, top = 0, cnt = 0, cnt2 = 0, Ans = 0, color = 0;
LL st[N << 2], du[N << 2], vis[N << 2], dfn[N << 2], low[N << 2], col[N << 2], head[N << 2];
LL ind[N], h2[N << 2], Maxr[N << 2], x[N], r[N], Minl[N << 2];
void add(LL x, LL y) { e[++cnt] = (edge) { head[x], y, x }, head[x] = cnt; }
void add2(LL x, LL y) { f[++cnt2] = (edge) { h2[x], y, x }, h2[x] = cnt2; }
void build(LL x, LL l, LL r)
{
if(l == r)
{
ind[l] = x;
return ;
}
LL mid = (l + r) >> 1;
build(x << 1, l, mid);
build(x << 1 | 1, mid + 1, r);
add(x, x << 1);
add(x, x << 1 | 1);
}
void update(LL x, LL l, LL r, LL stdl, LL stdr, LL std)
{
if(l > stdr || r < stdl) return ;
if(stdl <= l && stdr >= r)
{
if(x != std) add(std, x);
return ;
}
LL mid = (l + r) >> 1;
update(x << 1, l, mid, stdl, stdr, std);
update(x << 1 | 1, mid + 1, r, stdl, stdr, std);
}
void tarjan(LL x)
{
dfn[x] = low[x] = ++id;
st[++top] = x, vis[x] = 1;
for(LL i = head[x]; i; i = e[i].nxt)
{
LL v = e[i].to;
if(!dfn[v]) tarjan(v), low[x] = min(low[x], low[v]);
else if(vis[v]) low[x] = min(low[x], dfn[v]);
}
if(dfn[x] == low[x])
{
color++, st[top + 1] = -1;
while(st[top + 1] != x)
vis[st[top]] = 0, col[st[top--]] = color;
}
}
void top_sort()
{
queue <int> q;
for(LL i = 1; i <= color; i++) if(du[i] == 0) q.push(i);
while(!q.empty())
{
LL a = q.front();
q.pop();
for(LL i = h2[a]; i; i = f[i].nxt)
{
LL v = f[i].to;
Minl[v] = min(Minl[v], Minl[a]);
Maxr[v] = max(Maxr[v], Maxr[a]);
du[v]--;
if(du[v] == 0) q.push(v);
}
}
}
void Clear()
{
memset(du, 0, sizeof(du));
memset(h2, 0, sizeof(h2));
memset(vis, 0, sizeof(vis));
memset(low, 0, sizeof(low));
memset(dfn, 0, sizeof(dfn));
memset(head, 0, sizeof(head));
memset(Maxr, 0, sizeof(Maxr));
memset(Minl, 0x3f, sizeof(Minl));
return ;
}
int main()
{
scanf("%lld", &n);
Clear(), build(1, 1, n);
for(LL i = 1; i <= n; i++) scanf("%lld%lld", &x[i], &r[i]);
for(LL i = 1; i <= n; i++)
{
LL il = lower_bound(x + 1, x + n + 1, x[i] - r[i]) - x;
LL ir = upper_bound(x + 1, x + n + 1, x[i] + r[i]) - x - 1;
update(1, 1, n, il, ir, ind[i]);
}
tarjan(1);
for(LL i = 1; i <= n; i++)
{
Minl[col[ind[i]]] = min(Minl[col[ind[i]]], i);
Maxr[col[ind[i]]] = max(Maxr[col[ind[i]]], i);
}
for(LL i = 1; i <= cnt; i++)
if(col[e[i].f] != col[e[i].to])
add2(col[e[i].to], col[e[i].f]), du[col[e[i].f]]++;
top_sort();
for(LL i = 1; i <= n; i++)
Ans = (Ans + i * (Maxr[col[ind[i]]] - Minl[col[ind[i]]] + 1) + mod) % mod;
printf("%lld", Ans % mod);
return 0;
}