【数据结构 + 图论】P5025 [SNOI2017]炸弹

传送门:
(空间给的大,但数据强)
https://www.luogu.com.cn/problem/P5025

(数据弱)
https://www.acwing.com/problem/content/3042/

线段树优化建图 + 拓扑排序。

可以作为线段树优化建图模板题食用。

分析

对于这道题,如果直接建图,那么每个炸弹直接向它直接引爆的其它炸弹连边然后搜索显然会让复杂度达到 \(O(N^2)\),会超时。

注意到对于每个炸弹,它能够直接引爆的炸弹在区间上是连续的,所以我们考虑利用线段树来优化建图。

线段树优化建图

这是怎么做的呢?举例来说,你现在有 \(4\) 个点,你需要将 \(1\) 点向 \([3, 4]\) 连边,那么我们可以将 \([1, 4]\) 用线段树维护起来,如下图所示:

image

然后将 \(1\)\(7\) 号点连边就可以了:

image

在我看来,这个过程和虚树很像,也是通过建立虚点保证原来点之间的关系成立。

这样做,可以将 \(O(N^2)\) 级别的边数变成 \(O(NlogN)\) 级别,使得时间和空间复杂度都变得正确了。

本题思路

首先,我们利用线段树将图建起来,然后用 tarjan 缩点,将图变为 \(DAG\)​​​(从原图变为新图)​​,然而,我们很难较快地在一个一般的 \(DAG\)​​​​​​ 上求出每个点能够到达的点数,但是,因为这里的图比较特殊,每个炸弹直接或者间接引爆的炸弹的编号是连续的(直观上非常好理解),所以我们可以预处理出每个连通分量能够到达的最小的点(原图意义上)的编号和最大的点(原图意义上)的编号并统计答案即可。

实现

如果对线段树优化建图的实现仍然有困惑,可以试着模拟下面的数据:

4 
1 4 
3 3 
5 0 
6 1

它建立出来的图是:

image
// Problem: P5025 [SNOI2017]炸弹
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P5025
// Memory Limit: 512 MB
// Time Limit: 2500 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;

#define debug(x) cerr << #x << ": " << x << endl
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define dwn(i,a,b) for(int i=(a);i>=(b);i--)

using pii = pair<int, int>;
using ll = long long;

#define int ll

inline void read(int &x) {
    int s=0;x=1;
    char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-')x=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
    x*=s;
}

const int N=2e6+5, M=N+5e5*25, mod=1e9+7, INF=0x3f3f3f3f;

int n;
int p[N], r[N];

int idx;

struct Edge{
	int to, next;
}e[M];

int h[N], tot;

void add(int u, int v){
	e[tot].to=v, e[tot].next=h[u], h[u]=tot++;
}

int id[N];

#define ls u<<1
#define rs u<<1|1

int tmp;
void build(int u, int l, int r){
	if(l==r){
		id[u]=++tmp;
		return;
	}
	idx++;
	id[u]=idx;
	int mid=l+r>>1;
	build(ls, l, mid), build(rs, mid+1, r);
	add(id[u], id[ls]), add(id[u], id[rs]);
}

void link(int u, int l, int r, int node, int nl, int nr){
	if(nl<=l && r<=nr){
		add(node, id[u]);
		return;
	}
	int mid=l+r>>1;
	if(nl<=mid) link(ls, l, mid, node, nl, nr);
	if(mid<nr) link(rs, mid+1, r, node, nl, nr);
}

int dfn[N], low[N], ts;
int col[N], cnt;
int top, stk[N];
bool ins[N];
int le[N], ri[N];

void tarjan(int u){
	dfn[u]=low[u]=++ts;
	stk[++top]=u; ins[u]=true;
	for(int i=h[u]; ~i; i=e[i].next){
		int go=e[i].to;
		if(!dfn[go]){
			tarjan(go);
			low[u]=min(low[u], low[go]);
		}
		else if(ins[go]) low[u]=min(low[u], dfn[go]);
	}	
	
	if(dfn[u]==low[u]){
		cnt++;
		int y;
		le[cnt]=INF, ri[cnt]=-INF;
		do{
			y=stk[top--];
			ins[y]=false;
			col[y]=cnt;
			if(y<=n) le[cnt]=min(le[cnt], y), ri[cnt]=max(ri[cnt], y);
		}while(y!=u);
	}
}

vector<int> g[N];

void Add(int u, int v){
	g[u].push_back(v);
}

int deg[N];

int q[N];
int tt, hh;

void topsort(){
	tt=-1, hh=0;
	rep(i,1,cnt) if(!deg[i]) q[++tt]=i;
	while(tt>=hh){
		int u=q[hh++]; 
		for(auto go: g[u]){
			if(--deg[go]==0) q[++tt]=go;
			le[go]=min(le[go], le[u]);
			ri[go]=max(ri[go], ri[u]);
		}
	}
}

signed main(){
	memset(h, -1, sizeof h);
	cin>>n;
	rep(i,1,n) read(p[i]), read(r[i]);
	
	idx=n;
	build(1, 1, n);
	
	rep(i,1,n){
		int L=lower_bound(p+1, p+1+n, p[i]-r[i])-p;
		int R=upper_bound(p+1, p+1+n, p[i]+r[i])-p-1;
		link(1, 1, n, i, L, R);
	}

	rep(i,1,idx) if(!dfn[i]) tarjan(i);
	
	rep(i,1,idx) for(int j=h[i]; ~j; j=e[j].next){
		int go=e[j].to;
		int u=col[i], v=col[go];
		if(u!=v){
			Add(v, u); // 建反图
			deg[u]++;
		}
	}
	
	topsort();

	int res=0;
	rep(i,1,n) res=(res+i*(ri[col[i]]-le[col[i]]+1))%mod;
	cout<<res<<endl;
	
	return 0;
}
posted @ 2021-10-26 10:21  HinanawiTenshi  阅读(79)  评论(0编辑  收藏  举报