BZOJ5017【SNOI2017】炸弹题解(线段树优化建图+tarjan+DP)

本文详细解析了BZOJ5017题的解题思路,通过建立图模型并运用强连通分量缩点和DP算法,实现了在O(nlogn)的时间复杂度下求解序列中每个元素影响范围的问题。

题目:BZOJ5017.
题目大意:给定两个长度为 n n n的序列 x i , r i x_i,r_i xi,ri,若 x i − r i ≤ x j ≤ x i + r i x_i-r_i\leq x_j\leq x_i+r_i xirixjxi+ri,则称 i i i能影响 j j j;若 a a a能影响 b b b b b b能影响 c c c,则 a a a能影响 c c c.设 f ( i ) f(i) f(i)表示 i i i能影响的数的个数,求:
a n s = ∑ i = 1 n i ∗ f ( i )      ( m o d    1 0 9 + 7 ) ans=\sum_{i=1}^{n}i*f(i)\,\,\,\,(mod\,\,10^9+7) ans=i=1nif(i)(mod109+7)

1 ≤ n ≤ 5 ∗ 1 0 5 , x i − 1 &lt; x i 1\leq n\leq 5*10^5,x_{i-1}&lt;x_i 1n5105,xi1<xi.

首先,按照套路,我们把建 n n n个点,将第 i i i个点向第 i i i个点能够影响到的所有点连一条边.

然后我们就知道了,在这张图上 i i i能够到达的所有点都是能被 i i i影响的,所以我们需要统计 i i i能够到达的点的数目.

由于这张图可能有环出现,所以我们先跑个强连通分量缩点,缩完之后再统计就变成了在DAG上统计一个点能够到达的点的数目,可以很容易解决.

不幸的是,这个做法建图是 O ( n 2 ) O(n^2) O(n2)的,统计是 O ( n 2 ) O(n^2) O(n2)的.

不过没关系,容易发现每一个点连向的所有点必定是编号在一个连续区间内的点,所以线段树优化建图即可将建图复杂度降为 O ( n log ⁡ n ) O(n\log n) O(nlogn).

不过,统计DAG上一个点能够到达的点的数目好像怎么写都是 O ( n 2 ) O(n^2) O(n2)的(最多用bitset优化成 O ( n 2 32 ) O(\frac{n^2}{32}) O(32n2),然而要跑 50 W 50W 50W的数据还是算了吧…).

当然这道题是一定可以做的,所以我们要分析一个点可以到达点的性质.

容易发现一个点可以到达的所有点必然也是在一个连续区间内的,所以我们只要用DP求出这个区间的左右端点(可以到达节点的编号最大最小值),这个显然是可以做到现行复杂度解决的.

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn).

代码如下:

#include<bits/stdc++.h>
  using namespace std;
 
#define Abigail inline void
typedef long long LL;
 
const int N=500000,mod=1000000007,C=15,INF=(1<<30)-1;
 
int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
int sub(int a,int b){return a-b<0?a-b+mod:a-b;}
int mul(int a,int b){return (LL)a*b%mod;}
void sadd(int &a,int b){a=add(a,b);}
void ssub(int &a,int b){a=sub(a,b);}
void smul(int &a,int b){a=mul(a,b);}
 
int n;
LL a[N+9],r[N+9];
 
int Lower(LL x){
  int l=1,r=n,mid=l+r>>1;
  for (;l<r;mid=l+r>>1)
    x>a[mid]?l=mid+1:r=mid;
  return l;
}
 
int Upper(LL x){
  int l=1,r=n,mid=l+r+1>>1;
  for (;l<r;mid=l+r+1>>1)
    x<a[mid]?r=mid-1:l=mid;
  return l; 
}
 
struct side{
  int y,next;
}e[N*C*4+9];
int lin[2][N*4+9],cs;
 
void Ins(int id,int x,int y){e[++cs].y=y;e[cs].next=lin[id][x];lin[id][x]=cs;}
 
int tr[N*4+9],cn;
 
void Build(int L,int R,int k){
  if (L==R) {tr[k]=L;return;}
  tr[k]=++cn;
  int mid=L+R>>1;
  Build(L,mid,k<<1);Build(mid+1,R,k<<1|1);
  Ins(0,tr[k],tr[k<<1]);Ins(0,tr[k],tr[k<<1|1]);
}
 
void Link(int u,int L,int R,int l,int r,int k){
  if (L==l&&R==r) {Ins(0,u,tr[k]);return;}
  int mid=l+r>>1;
  if (R<=mid) Link(u,L,R,l,mid,k<<1);
  else if (L>mid) Link(u,L,R,mid+1,r,k<<1|1);
    else Link(u,L,mid,l,mid,k<<1),Link(u,mid+1,R,mid+1,r,k<<1|1);
}
 
int dfn[N*4+9],low[N*4+9],vis[N*4+9],co;
stack<int>sta;
int bel[N*4+9],cnt;
vector<int>scc[N*4+9];
 
void Tarjan(int k){
  dfn[k]=low[k]=++co;
  vis[k]=1;sta.push(k);
  for (int i=lin[0][k];i;i=e[i].next)
    if (!dfn[e[i].y]){
      Tarjan(e[i].y);
      low[k]=min(low[k],low[e[i].y]);
    }else if (vis[e[i].y]) low[k]=min(low[k],dfn[e[i].y]);
  if (dfn[k]^low[k]) return;
  ++cnt;
  while (vis[k]){
    int t=sta.top();sta.pop();
    vis[t]=0;
    scc[bel[t]=cnt].push_back(t);
  }
}
 
int mx[N*4+9],mn[N*4+9],ans;
 
void Contract(){
  for (int i=1;i<=cn;++i)
    if (!dfn[i]) Tarjan(i);
  for (int i=1;i<=cn;++i)
    for (int j=lin[0][i];j;j=e[j].next)
      if (bel[i]^bel[e[j].y]) Ins(1,bel[i],bel[e[j].y]);
}
 
void Dfs_dp(int k){
  if (vis[k]) return;
  vis[k]=1;
  for (int i=lin[1][k];i;i=e[i].next){
    Dfs_dp(e[i].y);
    mx[k]=max(mx[k],mx[e[i].y]);
    mn[k]=min(mn[k],mn[e[i].y]);
  }
}
 
Abigail into(){
  scanf("%d",&n);
  for (int i=1;i<=n;++i)
    scanf("%lld%lld",&a[i],&r[i]);
}
 
Abigail work(){
  cn=n;
  Build(1,n,1);
  for (int i=1;i<=n;++i)
    Link(i,Lower(a[i]-r[i]),Upper(a[i]+r[i]),1,n,1);
  Contract();
  for (int i=1;i<=cnt;++i) mx[i]=-INF,mn[i]=INF,vis[i]=0;
  for (int i=1;i<=n;++i){
    mx[bel[i]]=max(mx[bel[i]],Upper(a[i]+r[i]));
    mn[bel[i]]=min(mn[bel[i]],Lower(a[i]-r[i]));
  }
  for (int i=1;i<=n;++i){
    Dfs_dp(bel[i]);
    sadd(ans,mul(i,mx[bel[i]]-mn[bel[i]]+1));
  }
}
 
Abigail outo(){
  printf("%d\n",ans);
}
 
int main(){
  into();
  work();
  outo();
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值