BZOJ 5017 炸弹 线段树优化建图 Tarjan缩点 拓扑序 / 递推
[Snoi2017]炸弹
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 \rightarrow j $ 的边,但是由于题目中的边数较多,所以不可能这样建图 -
当一个炸弹被引爆,那么最终一共被引爆的炸弹一定是连续的,
所以就又引出区间问题了,那么区间问题我们就可以用线段树来做。 -
当 $ i $ 炸弹能将 $ [L,R] $ 的炸弹全部引爆时,就建一条 $ pos_i \rightarrow 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;
}