【LOJ#2255】炸弹
题目
题目链接:https://loj.ac/problem/2255
在一条直线上有 \(n\) 个炸弹,每个炸弹的坐标是 \(x_i\),爆炸半径是 \(r_i\),当一个炸弹爆炸时,如果另一个炸弹所在位置 \(x_j\) 满足 \(|x_j-x_i|\le r_i\) ,那么,该炸弹也会被引爆。
现在,请你帮忙计算一下,先把第 \(i\) 个炸弹引爆,将引爆多少个炸弹呢?
答案对 \(10^9 + 7\) 取模。
思路
首先显然每个炸弹炸的范围是一个区间。我们可以先求出这个区间 \([p_i,q_i]\)。
然后如果我们将 \(i\) 向满足 \(x\in [p_i,i)∪(i,q_i]\),那么就将 \(i\) 向 \(x\) 连边。然后在这张图中,每个点能到达的点就是能炸到的炸弹。
然而这种算法时空复杂度都不够优秀。考虑优化。
首先解决空间复杂度,即建图的问题。普通的建图最高复杂度会达到 \(O(n^2)\)。但是由于每一个炸弹炸到的范围是一个区间,我们可以用线段树优化建图。线段树的每个子节点向父亲连边,然后对于每一个点 \(i\),将 \([p_i,i)\) 和 \((i,q_i]\) 的在线段树中的区间连向 \(i\)。这样每个点最多连 \(\log n\) 条边,空间复杂度 \(O(n\log n)\)。
接下来考虑如何求每个点能到达哪些点。显然的,先将图缩点,变成一张 DAG。由于每一个炸弹爆炸到的点是一个范围,所以可以直接维护每一个 SCC 能爆炸到的最小/最大编号的炸弹,那么这个 SCC 能炸到的点数量即为两者的差加一。
接下来处理 DAG 中的边。显然可以用 topsort 来解决不是一个 SCC 内的点的爆炸关系。具体的,如果在 DAG 中有一条边 \((x,y)\),那么就让 \(y\) 所能爆炸到的最值与 \(x\) 所能爆炸到的最值取 \(\operatorname{min/max}\)。
那么最终假设点 \(i\) 处于第 \(col[i]\) 个 SCC,那么点 \(i\) 做出的贡献即为 \(i\times (R[col[i]]-L[col[i]]+1)\)。其中 \(L,R\) 分别为这个 SCC 所能爆炸到的点的编号范围。
时间复杂度 \(O(n\log n+n)\)。在洛谷上吸氧能过,LOJ 上直接过了。
代码
#include <stack>
#include <queue>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define reg register
using namespace std;
typedef long long ll;
const int N=2000010,M=500010*30,MOD=1e9+7;
int head[M],From[M],To[M],id[N],dfn[N],low[N],col[N],p[N],q[N],deg[N],L[N],R[N];
int n,tot,Maxn,cnt;
ll ans,pos[N],len[N];
bool vis[N];
stack<int> st;
struct edge
{
int next,to;
}e[M];
inline ll read()
{
ll d=0,f=1; char ch=getchar();
while (!isdigit(ch)) f=(ch=='-'?-1:f),ch=getchar();
while (isdigit(ch)) d=(d<<3)+(d<<1)+ch-48,ch=getchar();
return d*f;
}
void add(int from,int to)
{
e[++tot].to=to;
e[tot].next=head[from];
head[from]=tot;
From[tot]=from; To[tot]=to;
}
struct SegTree
{
int l[M],r[M];
void build(int x,int ql,int qr)
{
l[x]=ql; r[x]=qr;
Maxn=max(Maxn,x);
if (ql==qr)
{
id[ql]=x;
return;
}
int mid=(ql+qr)>>1;
build(x*2,ql,mid);
build(x*2+1,mid+1,qr);
add(x*2,x); add(x*2+1,x);
}
void update(int x,int ql,int qr,int i)
{
if (ql>qr) return;
if (ql==l[x] && qr==r[x])
{
add(x,id[i]);
return;
}
int mid=(l[x]+r[x])>>1;
if (qr<=mid) update(x*2,ql,qr,i);
else if (ql>mid) update(x*2+1,ql,qr,i);
else update(x*2,ql,mid,i),update(x*2+1,mid+1,qr,i);
}
}seg;
void tarjan(int x)
{
dfn[x]=low[x]=++tot;
st.push(x); vis[x]=1;
for(reg int i=head[x];~i;i=e[i].next)
{
int 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])
{
int y; cnt++;
do {
y=st.top(); st.pop();
vis[y]=0; col[y]=cnt;
L[cnt]=min(L[cnt],p[y]);
R[cnt]=max(R[cnt],q[y]);
} while (x!=y);
}
}
inline void topsort()
{
queue<int> Q;
for(reg int i=1;i<=cnt;i++)
if (!deg[i]) Q.push(i);
while (Q.size())
{
int u=Q.front(); Q.pop();
for(reg int i=head[u];~i;i=e[i].next)
{
int v=e[i].to;
L[v]=min(L[v],L[u]);
R[v]=max(R[v],R[u]);
deg[v]--;
if (!deg[v]) Q.push(v);
}
}
}
int main()
{
memset(head,-1,sizeof(head));
memset(p,0x3f3f3f3f,sizeof(p));
memset(L,0x3f3f3f3f,sizeof(L));
n=read();
seg.build(1,1,n);
for(reg int i=1;i<=n;i++)
pos[i]=read(),len[i]=read();
pos[0]=-9223372036854775808LL;
pos[n+1]=9223372036854775807LL;
for(reg int i=1;i<=n;i++)
{
p[id[i]]=lower_bound(pos,pos+n+2,pos[i]-len[i])-pos;
q[id[i]]=upper_bound(pos,pos+n+2,pos[i]+len[i])-pos-1;
seg.update(1,p[id[i]],i-1,i);
seg.update(1,i+1,q[id[i]],i);
}
int tot2=tot;
tot=0;
for(reg int i=1;i<=Maxn;i++)
if (!dfn[i]) tarjan(i);
memset(head,-1,sizeof(head));
tot=0;
for(reg int i=1;i<=tot2;i++)
{
int x=col[From[i]],y=col[To[i]];
if (x!=y)
{
add(x,y);
deg[y]++;
}
}
topsort();
for(reg int i=1;i<=n;i++)
ans=(ans+1LL*i*(R[col[id[i]]]-L[col[id[i]]]+1))%MOD;
printf("%lld",ans);
return 0;
}