我们从一道例题开始。

CF786B

Description

Solution

朴素解法: 暴力连边+最短路

对于每次连边操作,我们逐一连边,最后在图上跑一遍单源最短路径算法即可。

时间复杂度 O ( n 2 log ⁡ ( n 2 ) ) O(n^2 \log (n^2)) O(n2log(n2))。

正解: 线段树优化建图

线段树有一个非常优美的性质: 区间 [ l , r ] [l,r] [l,r]可以被映射成线段树上的许多连续的区间,且这些区间的数量不超过 ⌈ log ⁡ n ⌉ \lceil \log n \rceil ⌈logn⌉。

我们要巧妙运用这个性质——我们是否可以将每一个连边的区间 [ l , r ] [l,r] [l,r]映射到线段树上的 log ⁡ \log log个节点,然后只向这 log ⁡ \log log个节点连边呢?

答案是可以的。我们建立两棵线段树,一个线段树往内连边(简称为入树),另一个线段树往外连边(简称为出树)。每棵树的叶节点对应图中一个的真实节点。同时,两棵树中对应的叶节点连一条边权为 0 0 0的有向边(即下图中五彩缤纷的那些边)。同一棵树中的父节点与孩子节点也要连一条边权为 0 0 0的有向边。

对于每次连边操作,我们只向 log ⁡ \log log个节点连一条有向边,边权为 w w w。

上图表示一个形如“从 1 1 1号节点向区间 [ 3 , 8 ] [3,8] [3,8]中的点分别连一条边”的第二类操作。第三类操作同理。第一类操作直接将对应的叶节点连边。

最后我们跑一遍单源最短路(Dijkstra)即可。

注意,这里的最短路的“源”是出树中表示区间 [ 1 , 1 ] [1,1] [1,1]的叶节点。

由于边数为 O ( n log ⁡ n ) O(n \log n) O(nlogn)级别,所以总时间复杂度为 O ( n log ⁡ n log ⁡ ( n log ⁡ n ) ) ≈ O ( n l o g 2 n ) O(n \log n \log (n \log n))≈O(n\ log^2 n) O(nlognlog(nlogn))≈O(n log2n)。

Code

#include <bits/stdc++.h>
#define int long long
#define inf 200000000000007
using namespace std;
const int maxl=100005,maxg=20;int read(){int s=0,w=1;char ch=getchar();while (ch<'0'||ch>'9'){if (ch=='-')  w=-w;ch=getchar();}while (ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}return s*w;
}
int n,m,s,blo,opt,u,l,r,w,cnt=0;
int head[8*maxl],itree[4*maxl],otree[4*maxl],pos[maxl];
int dis[8*maxl],vis[8*maxl];struct edge{int nxt,to,dis;}e[4*maxg*maxl];//边数可能较多
struct node{int dis,pos;bool operator < (const node &x) const{return x.dis<dis;}
};void add_edge(int u,int v,int w){cnt++;e[cnt].to=v,e[cnt].dis=w,e[cnt].nxt=head[u];head[u]=cnt;
}void build_itree(int l,int r,int rt){if (l==r){pos[l]=rt;add_edge(rt+blo,rt,0),add_edge(rt,rt+blo,0);return;}int mid=(l+r)>>1;add_edge(rt,2*rt,0),build_itree(l,mid,2*rt);add_edge(rt,2*rt+1,0),build_itree(mid+1,r,2*rt+1);
}void build_otree(int l,int r,int rt){if (rt!=1)  add_edge(blo+rt,blo+(rt/2),0); if (l==r)  return;int mid=(l+r)>>1;build_otree(l,mid,2*rt);build_otree(mid+1,r,2*rt+1);
}void tree_edge(int nl,int nr,int l,int r,int rt,int link_pos,int ww,int k){if (nl<=l&&r<=nr){if (k==0)  add_edge(link_pos+blo,rt,ww);else add_edge(rt+blo,link_pos,ww);return;}int mid=(l+r)>>1;if (nl<=mid)  tree_edge(nl,nr,l,mid,2*rt,link_pos,ww,k);if (nr>mid)  tree_edge(nl,nr,mid+1,r,2*rt+1,link_pos,ww,k);
}std::priority_queue<node> q;
void dijkstra(){q.push((node){0,s});dis[s]=0;while (!q.empty()){int now=q.top().pos;q.pop();if (vis[now])  continue;vis[now]=1;for (int i=head[now];i;i=e[i].nxt){int y=e[i].to;if (dis[y]>dis[now]+e[i].dis){dis[y]=dis[now]+e[i].dis;if (!vis[y])  q.push((node){dis[y],y});}}}
}signed main(){n=read(),m=read(),s=read();blo=4*n;build_itree(1,n,1);build_otree(1,n,1);//给树中的每个节点一个统一的编号for (int i=1;i<=m;i++){opt=read(),u=read();if (opt==1){l=read(),w=read();tree_edge(l,l,1,n,1,pos[u],w,0);}else if (opt==2){l=read(),r=read(),w=read();tree_edge(l,r,1,n,1,pos[u],w,0);}else if (opt==3){l=read(),r=read(),w=read();tree_edge(l,r,1,n,1,pos[u],w,1);}}s=blo+pos[s];for (int i=1;i<=8*n;i++)  dis[i]=inf;dijkstra();for (int i=1;i<=n;i++){if (dis[pos[i]]>=inf)  dis[pos[i]]=-1;}for (int i=1;i<=n;i++)  printf("%lld ",dis[pos[i]]);return 0;
}

一个小扩展: 第四类操作

第四类操作是区间向区间连边,即 [ l 1 , r 1 ] [l_1,r_1] [l1​,r1​]中的每个节点向 [ l 2 , r 2 ] [l_2,r_2] [l2​,r2​]中的每个节点连一条边权为 w w w的边。

对于这一种新的操作我们该怎么办呢?不难想到,我们可以新建一个虚点 p p p,然后就变成了“ [ l 1 , r 1 ] [l_1,r_1] [l1​,r1​]向 p p p连边”与“ p p p向 [ l 2 , r 2 ] [l_2,r_2] [l2​,r2​]连边”,分别处理即可。

显然第四类操作并没有对时间复杂度有太大的影响(就是常数变大了好多……),依然是 O ( n log ⁡ 2 n ) O(n \log^2 n) O(nlog2n)。

例题: P5025

Description

Solution

算法一: 套路,朴素,时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( n 2 32 ) O(\frac {n^2} {32}) O(32n2​)

对于两个能够互相引爆的节点我们连一条边。

显然,所有第 i i i个炸弹能够引爆的炸弹就是所有从 i i i出发能够到达的炸弹。这是一个可达性统计问题。我们采用 b i t s e t bitset bitset去转移即可。

算法二: 连边的性质与线段树优化建图

不难发现,第 i i i个节点连向的所有节点一定在一个连续的区间 [ L , R ] [L,R] [L,R]内。

于是,我们可以对于每一个 i i i二分出 L L L和 R R R然后线段树优化建图即可。最后再跑一遍可达性统计。

时间复杂度与空间复杂度不变。

算法三: 小性质得到正解

考虑第 i i i个节点可达的节点映射下来一定是一个区间。

所以我们并不需要 b i t s e t bitset bitset这种大空间&大时间复杂度写法,我们只需要求出从第 i i i个节点能够到达的区间的左右端点即可,区间长度即为可达的炸弹数量。这可以通过缩点+ D A G DAG DAG上 D P DP DP求出。

时间复杂度被我们优化成了 O ( n log ⁡ n ) O(n \log n) O(nlogn)。

Code

#include <bits/stdc++.h>
#define ll long long
#define inf 2000000007
using namespace std;
const int maxl=500005,maxt=1500005,maxg=19,mod=1e9+7;ll read(){ll s=0,w=1;char ch=getchar();while (ch<'0'||ch>'9'){if (ch=='-')  w=-w;ch=getchar();}while (ch>='0'&&ch<='9'){s=(s<<1ll)+(s<<3ll)+(ch^'0');ch=getchar();}return s*w;
}
int n,tot,blo,len,cnt,nt;ll ans=0;
int head[maxt],tree[maxl*2][2],dfn[maxt],low[maxt],s[maxt];
int fa[maxt],lm[maxt],rm[maxt],inde[maxt];
ll a[maxl],b[maxl];bitset<maxt> vis,is_fa;
queue<int> q;
map<pair<int,int>,bool> ma;struct node{int x,y;}edge_lis[2*maxl+maxl*maxg];
struct edge{int nxt,to;}e[2*maxl+maxl*maxg];void add_edge(int u,int v){cnt++;e[cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;
}void clear_edges(){cnt=0;for (int i=1;i<=3*n;i++)  head[i]=0;
}int build_tree(int l,int r,int rt){rt=++tot;if (l==r){add_edge(rt,2*n+l);add_edge(2*n+l,rt);return rt;}int mid=(l+r)>>1;tree[rt][0]=build_tree(l,mid,0);tree[rt][1]=build_tree(mid+1,r,0);add_edge(rt,tree[rt][0]);add_edge(rt,tree[rt][1]);return rt;
}void tree_edge(int nl,int nr,int l,int r,int rt,int k){if (nl<=l&&r<=nr){add_edge(k,rt);return;}int mid=(l+r)>>1;if (nl<=mid)  tree_edge(nl,nr,l,mid,tree[rt][0],k);if (nr>mid)  tree_edge(nl,nr,mid+1,r,tree[rt][1],k);
}void tarjan(int now){dfn[now]=low[now]=++nt;s[++len]=now,vis[now]=1;for (int i=head[now];i;i=e[i].nxt){int y=e[i].to;if (!dfn[y]){tarjan(y);low[now]=min(low[now],low[y]);}else if (vis[y])  low[now]=min(low[now],dfn[y]);}if (dfn[now]==low[now]){int y;while (y=s[len]){fa[y]=now,vis[y]=0;len--;if (y>2*n){lm[now]=min(lm[now],y-2*n);rm[now]=max(rm[now],y-2*n);}if (y==now)  break;}}
}signed main(){n=read();build_tree(1,n,0);for (int i=1;i<=n;i++)  a[i]=read(),b[i]=read();for (int i=1;i<=n;i++){int p=lower_bound(a+1,a+n+1,a[i]-b[i])-a;int p2=upper_bound(a+1,a+n+1,a[i]+b[i])-a-1;if (p<p2&&p>=1&&p2<=n)  tree_edge(p,p2,1,n,1,i+2*n);}for (int i=1;i<=3*n;i++)  lm[i]=inf,rm[i]=0;tarjan(1);nt=0;for (int i=1;i<=3*n;i++)  is_fa[fa[i]]=1;for (int now=1;now<=3*n;now++){for (int i=head[now];i;i=e[i].nxt){int y=e[i].to;if (fa[now]!=fa[y]){edge_lis[++nt].x=fa[y];edge_lis[nt].y=fa[now];}}}clear_edges();for (int i=1;i<=nt;i++){int fx=edge_lis[i].x,fy=edge_lis[i].y;if (!ma[make_pair(fx,fy)]){add_edge(fx,fy);inde[fy]++;ma[make_pair(fx,fy)]=ma[make_pair(fy,fx)]=1;}}for (int i=1;i<=3*n;i++){if (is_fa[i]&&(!inde[i]))  q.push(i);}while (!q.empty()){int now=q.front();q.pop();for (int i=head[now];i;i=e[i].nxt){int y=e[i].to;inde[y]--;lm[y]=min(lm[y],lm[now]);rm[y]=max(rm[y],rm[now]);if (!inde[y])  q.push(y);}}for (int i=1;i<=n;i++)ans=(ans+1ll*i*(rm[fa[i+2*n]]-lm[fa[i+2*n]]+1ll)%mod)%mod;cout<<ans<<endl;return 0;
}

线段树优化建图详解——区间连边之技巧,吊打紫题之利器相关推荐

  1. 【CF1045A】A Last chance【贪心】【线段树优化建图】【网络流构造方案】

    题意:有nnn个武器和mmm个飞船,武器有下面三种 从给定的集合SSS中击破一个. 在给定的区间[L,R][L,R][L,R]中击破一个. 对于给定的a,b,ca,b,ca,b,c,选择000个或22 ...

  2. Codeforces 786B Legacy (线段树优化建图)

    Codeforces 786B Legacy (线段树优化建图) 题意:\(n\)个点,有\(3\)种连边操作:1.将\(u\)指向\(v\):2.将\(v\)指向编号在区间\([l,r]\)的点:3 ...

  3. Gym - 102174G 神圣的 F2 连接着我们 (线段树优化建图 + 多源最短路)

    Description 小白非常喜欢玩 "县际争霸" 这款游戏,虽然他的技术并不容乐观."县际争霸" 的地图共有两个县,每个县里各有 n n n 个据点.同一个 ...

  4. 洛谷P3588 [POI2015]PUS(线段树优化建图)

    题面 传送门 题解 先考虑暴力怎么做,我们把所有\(r-l+1-k\)中的点向\(x\)连有向边,表示\(x\)必须比它们大,那么如果这张图有环显然就无解了,否则的话我们跑一个多源最短路,每个点的\( ...

  5. P6348 [PA2011]Journeys 线段树优化建图 区间连区间

    传送门 文章目录 题意: 思路: 题意: 每次连接[a,b][a,b][a,b]与[c,d][c,d][c,d]之间所有点,让后跑最短路. 思路: 比普通的优化建图能简单点,我们只需要加两个虚点之间边 ...

  6. BZOJ.3218.a + b Problem(最小割ISAP 可持久化线段树优化建图)

    BZOJ UOJ 首先不考虑奇怪方格的限制,就是类似最大权闭合子图一样建图. 对于奇怪方格的影响,显然可以建一条边\((i\to x,p_i)\),然后由\(x\)向\(1\sim i-1\)中权值在 ...

  7. CF786B Legacy(线段树优化建图)

    传送门 ovo这题该怎么做呢?我们首先考虑暴力建图,但是因为建图的操作太多直接就会MLE,所以这个就别想了-- 我们考虑如何优化建图.因为发现一个点可以向一个区间连边,一个区间也可以向一个点连边,想到 ...

  8. CodeForces - 787D - Legacy(线段树优化建图+最短路)

    题目链接:点击查看 题目大意:给出 nnn 个点和 mmm 条边,现在需要求从 ststst 开始到所有点的最短路是多少,mmm 条边的给出方式如下: 1uvw1 \ u \ v \ w1 u v w ...

  9. P5025-[SNOI2017]炸弹【tarjan,线段树优化建图】

    正题 题目链接:https://www.luogu.com.cn/problem/P5025 题目大意 .nnn个炸弹,每个在xxx位置处,范围为rrr.定义fif_ifi​表示第iii个炸弹爆炸能连 ...

最新文章

  1. 自问自答:在VB中如何实现像C++一样printf的功能
  2. js indexof用法indexOf()定义和用法
  3. Python平衡点问题
  4. php Collection类的设计
  5. Android 创世纪 第二天
  6. java socket-服务器不启动 直接启动客户端.md
  7. 关于某系统的全面质量属性战术
  8. CentOS6.7 mysql5.6.33修改数据文件位置
  9. C#获取当前路径的方法如下
  10. mysql锁机制原理,一招彻底弄懂!
  11. 开源视频平台:ViMP
  12. 机器学习-西瓜书、南瓜书第四章
  13. uniapp引入字体包——DIN
  14. 在virtual box虚拟机上下载sniffer pro
  15. Fabric实战(12)Fabric CA-账号服务器
  16. 从写作到演讲,虾米君不断尝试的 2021|年终回顾
  17. 知乎客户端埋点流程、模型和平台技术
  18. 阿里云服务器中安装配置MYSQL数据库完整教程
  19. matlab解不定,matlab解不定方程
  20. 【wpf】<i:Interaction.Triggers> 相关报错

热门文章

  1. 简易入手《SOM神经网络》的本质与原理
  2. 将本地项目上传至码云仓库
  3. 2019——区块链从业者的集体冬眠
  4. 发票扫一扫识别,一键导出表格
  5. Android“应用未安装”的解决办法
  6. UNABLE TO READ CONSUMER IDENTITY
  7. 函数<小罗爱c语言>
  8. 6.Spring security中的rememberMe
  9. Unity-URP学习笔记(四)赛璐珞高光
  10. yjp-9.5.6使用