【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;
}
posted @ 2020-06-15 21:11  stoorz  阅读(141)  评论(0编辑  收藏  举报