Description
在一条直线上有 N 个炸弹,每个炸弹的坐标是 Xi,爆炸半径是 Ri,当一个炸弹爆炸时,如果另一个炸弹所在位置 Xj 满足:
Xi−Ri≤Xj≤Xi+Ri,那么,该炸弹也会被引爆。
现在,请你帮忙计算一下,先把第 i 个炸弹引爆,将引爆多少个炸弹呢?
Input
第一行,一个数字 N,表示炸弹个数。
第 2∼N+1行,每行 2 个数字,表示 Xi,Ri,保证 Xi 严格递增。
N≤500000
−10^18≤Xi≤10^18
0≤Ri≤2×10^18
Output
一个数字,表示Sigma(i*炸弹i能引爆的炸弹个数),1<=i<=N mod10^9+7。
如果一个炸弹能引爆另一个,就对应连一条有向边,询问即为查询每个点能到达的点中最左和最右分别是哪一个。由于边数较多,需要线段树优化建图,然后用tarjan将强连通分量缩点,缩点后在得到的DAG上逆拓扑序递推一下。时间复杂度O(nlogn),空间复杂度O(n)(线段树上的区间连边不需要记录,只要动态计算即可)。
#include<bits/stdc++.h> typedef long long i64; const int P=1e9+7,N=5e5+7; char buf[N*50],*ptr=buf-1; i64 _(){ i64 x=0; int c=*++ptr,f=1; while(c<48)c=='-'?f=-1:0,c=*++ptr; while(c>47)x=x*10+c-48,c=*++ptr; return x*f; } int n,mx,tk=0,ss[1<<20|111],sp=0,ans=0; i64 xs[N],rs[N]; void mins(int&a,int b){if(a>b)a=b;} void maxs(int&a,int b){if(a<b)a=b;} struct node{ int l,r,dfn,low; bool in; void chk0(node&w){ mins(l,w.l),maxs(r,w.r); } void chk1(node&w){ mins(low,w.low); chk0(w); } void chk2(node&w){ if(w.in)mins(low,w.dfn); chk0(w); } }ns[1<<20|111]; void tj(int); void tje(int w,int u){ if(!ns[u].dfn){ tj(u); ns[w].chk1(ns[u]); }else ns[w].chk2(ns[u]); } void tj(int w){ ns[w].dfn=ns[w].low=++tk; ns[w].in=1; ss[++sp]=w; if(!ns[w].l)ns[w].l=n+1; if(w<mx){ tje(w,w<<1); tje(w,w<<1^1); }else{ for(int l=mx+ns[w].l-1,r=mx+ns[w].r+1;r-l>1;l>>=1,r>>=1){ if(~l&1)tje(w,l+1); if(r&1)tje(w,r-1); } } if(ns[w].dfn==ns[w].low){ int u; do{ ns[u=ss[sp--]].in=0; ns[u].chk0(ns[w]); }while(u!=w); } } int main(){ fread(buf,1,sizeof(buf),stdin); n=_(); for(int i=1;i<=n;++i)xs[i]=_(),rs[i]=_(); for(mx=1;mx<=n+2;mx<<=1); for(int i=1;i<=n;++i){ ns[mx+i].l=std::lower_bound(xs+1,xs+n+1,xs[i]-rs[i])-xs; ns[mx+i].r=std::upper_bound(xs+1,xs+n+1,xs[i]+rs[i])-xs-1; } tj(1); for(int i=1;i<=n;++i){ node&w=ns[mx+i]; ans=(ans+i64(i)*(w.r-w.l+1))%P; } printf("%d\n",ans); return 0; }