BZOJ 5017 炸弹 线段树优化建图 Tarjan缩点 拓扑序 / 递推

$ Rightarrow $ 戳我进BZOJ原题

[Snoi2017]炸弹

Time Limit: 30 Sec $ quad $ Memory Limit: 512 MB
 

Description

在一条直线上有 $ N $ 个炸弹,每个炸弹的坐标是 $ X_i $ ,爆炸半径是 $ R_i $ ,当一个炸弹爆炸时,如果另一个炸弹所在位置 $ X_j $ 满足:
$ X_i−R_i≤X_j≤X_i+R_i $ ,那么,该炸弹也会被引爆。
现在,请你帮忙计算一下,先把第 $ i $ 个炸弹引爆,将引爆多少个炸弹呢?
 

Input

第一行,一个数字 $ N $,表示炸弹个数。
第 $ 2∼N+1 $ 行,每行 $ 2 $ 个数字,表示 $ X_i,R_i $ ,保证 $ X_i $ 严格递增。
$ N≤500000 $
$ −1018≤X_i≤1018 $
$ 0≤R_i≤2×10^18 $
 

Output

一个数字,表示$ sum(i*炸弹i能引爆的炸弹个数),1<=i<=N mod10^9+7 $ 。
 

Sample Input

 4
 1 1
 5 1
 6 5
 15 15

Sample Output

 32

 

思路

  • 当一个炸弹 $ i $ 被引爆时,对于能过够被当前这颗炸弹 $ i $ 引爆的炸弹 $ j $
    我们肯定要建一条 $ i ightarrow j $ 的边,但是由于题目中的边数较多,所以不可能这样建图

  • 当一个炸弹被引爆,那么最终一共被引爆的炸弹一定是连续的,
    所以就又引出区间问题了,那么区间问题我们就可以用线段树来做。

  • 当 $ i $ 炸弹能将 $ [L,R] $ 的炸弹全部引爆时,就建一条 $ pos_i ightarrow now $ 的边
    那么这样就可能会形成环,因为这颗炸弹处于区间中间,那么就 $ Tarjan $ 缩点。

  • 最后 $ Topsort $ ,反向建图,从最远的点一步一步地推回去,从而解决题目。
     

代码

/**************************************************************
    Problem: 5017
    User: PotremZ
    Language: C++
    Result: Accepted
    Time:8472 ms
    Memory:469120 kb
****************************************************************/
 
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define Mod 1000000007
#define N 1000005
const long long inf=4e18;
vector<int>e[N<<2],G[N<<2];
int pos[N<<2],mps[N<<2],maxo;
void build(int o,int l,int r){
    maxo=max(maxo,o);
    if(l==r){
        pos[l]=o;
        mps[o]=l;
        return;
    }
    int mid=l+r>>1;
    build(o<<1,l,mid); build(o<<1|1,mid+1,r);
    e[o].push_back(o<<1);
    e[o].push_back(o<<1|1);
}
void updata(int o,int l,int r,int L,int R,int v){
    if(L<=l&&r<=R){
        if(o==v) return;
        e[v].push_back(o);
        return;
    }
    int mid=l+r>>1;
    if(L>mid) updata(o<<1|1,mid+1,r,L,R,v);
    else if(R<=mid) updata(o<<1,l,mid,L,R,v);
    else {
        updata(o<<1,l,mid,L,R,v);
        updata(o<<1|1,mid+1,r,L,R,v);
    }
}
stack<int>s;
int dfn[N<<2],low[N<<2],tim,scc,sR[N<<2],sL[N<<2],bel[N<<2],x[N],r[N];
bool vis[N<<2];
inline void tarjan(int u){
    dfn[u]=low[u]=++tim; s.push(u); vis[u]=1;
    for(int i=0;i<e[u].size();++i){
        int v=e[u][i];
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        } else if(vis[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u]){
        ++scc;
        sL[scc]=inf; sR[scc]=-inf;
        do{
            u=s.top(); s.pop(); 
            bel[u]=scc; vis[u]=0;
            if(mps[u]){
                sR[scc]=max(sR[scc],x[mps[u]]+r[mps[u]]);
                sL[scc]=min(sL[scc],x[mps[u]]-r[mps[u]]);
            }
        }while(low[u]!=dfn[u]);
    }
}
int n,in[N<<2],ans;
queue<int>q;
signed main(){
    scanf("%lld",&n);
    build(1,1,n);
    for(int i=1;i<=n;++i)
         scanf("%lld %lld",&x[i],&r[i]);
    for(int i=1;i<=n;++i){
        int L=lower_bound(x+1,x+1+n,x[i]-r[i])-x;
        int R=upper_bound(x+1,x+1+n,x[i]+r[i])-x-1;
        updata(1,1,n,L,R,pos[i]);
    }
    for(int i=1;i<=maxo;++i) if(!dfn[i]) tarjan(i);
    for(int i=1;i<=maxo;++i)
        for(int j=0;j<e[i].size();++j){
            int v=e[i][j];
            if(bel[i]!=bel[v]){
                G[bel[v]].push_back(bel[i]);
                ++in[bel[i]];
            }
        }
    for(int i=1;i<=scc;++i){
        if(in[i]==0) q.push(i);
    }
    while(!q.empty()){
        int u=q.front(); q.pop();
        for(int i=0;i<G[u].size();++i){
            int v=G[u][i];
            sL[v]=min(sL[v],sL[u]);
            sR[v]=max(sR[v],sR[u]);
            --in[v];
            if(in[v]==0) q.push(v);
        }
    }
    for(int i=1;i<=n;++i){
        int L=lower_bound(x+1,x+n+1,sL[bel[pos[i]]])-x;
        int R=upper_bound(x+1,x+n+1,sR[bel[pos[i]]])-x-1;
        ans=(ans+(R-L+1)*i%Mod)%Mod;
    }
    printf("%lld",ans);
    return 0;
}

 

PS.

这道题其实可以 $ O(n) $ 递推过,递推思想和上面差不多,就直接放代码了

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define maxn 500010
#define int long long
#define mod 1000000007
int n,x[maxn],r[maxn],L[maxn],R[maxn],ans;
signed main(){
	scanf("%lld",&n);
	for(int i=1;i<=n;++i) scanf("%lld %lld",&x[i],&r[i]);
	for(int i=1;i<=n;++i){
		L[i]=i;
		while(L[i]>1&&x[i]-x[L[i]-1]<=r[i]){
			L[i]=L[L[i]-1];
			r[i]=max(r[i],r[L[i]]-(x[i]-x[L[i]]));
		}
	}
	for(int i=n;i;--i){
		R[i]=i;
		while(R[i]<n&&x[R[i]+1]-x[i]<=r[i]){
			R[i]=R[R[i]+1]; 
			L[i]=min(L[i],L[R[i]]);
		}
	}
	for(int i=1;i<=n;++i) ans=(ans+i*(R[i]-L[i]+1)%mod)%mod;
	printf("%lld",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/PotremZ/p/BZOJ5017.html