一些概念

无向图:

连通图:在无向图中,任意两点都直接或间接连通,则称该图为连通图。(或者说:任意两点之间都存在可到达的路径)

连通分量: G的 最大连通子图 称为G的连通分量

有向图 (ps.区别在与“强”)

强连通图: 在有向图中,对于每一对顶点Vi,Vj都存在从Vi到Vj和从Vj到Vi的路径(任意两点之间都存在可到达对方的路径),则称该图为强连通图

强连通分量有向图G的 最大强连通子图 称为G的强连通分量

求强连通分量+有向图的压缩(缩点)

缩点即讲一个强连通分量中的点标记为同一类,看作一个大点包含所有其中点的信息

可以用Kosaraju但是,这里讲的是精妙的Tarjan算法

背景:dfs序

思想:做一遍DFS,用dfn[i]表示编号为i的节点在DFS过程中的访问序号(也可以叫做开始时间)用low[i]表示i节点及其下方节点所能到达的开始时间最早的节点的 开始时间 。初始时dfn[i]=low[i]

ps.大家一开始不要过于纠结low的意思,很多时候是根据需求和用途来的

important:

如果一个节点的low值等于dfn值,则说明其下方的节点不能走到其上方的节点,那么该节点u就是一个强连通分量在DFS搜索树中的根。

但是u的子孙节点未必就和u处于同一个强连通分量,怎样找出u的子孙中,哪些跟它位于同一强连通分量?

这里需要用到

栈的用途是保存搜过的结点并能及时删除,先上代码

int dfn[N],low[N],Time,SCC=0,Belong[N],In_du[N];
bool In_stack[N];
stack<int> st;
void Tarjan(int u) {
dfn[u]=low[u]=++Time;
st.push(u); In_stack[u]=true;
for(int v=1;v<=n;v++) {
if(mp[u][v]&&!dfn[v]) {
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(mp[u][v]&&dfn[v]!=0&&In_stack[v]) {
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u]) {
++SCC; int v;
do {
v=st.top(); st.pop();
In_stack[v]=false; Belong[v]=SCC; //belong 缩点
}while(v!=u); //直到删除了最高点
}
}

注意的是理解代码的In_stack[]标记,

因为如果你不可以反祖边指向了一个被遍历过且已经出栈的结点。(一个点一旦出了栈,它便有了belong)


缩点使图缩为DAG图

因此拓扑排序(图上的动态规划),在此前许会用缩点预处理

此处设一道基础例题:【APIO2009】抢掠计划

题意是:有向图中,有些点是酒吧,有的不是。每一个点都有它的价值,不过你只能抢一次并加上该价值。你从s出发到任意一个有酒吧的点结束。问您能抢到的最大价值是多少。

方程是:dp[v] = Max{ dp[u]+len[u][v]; }

为了保证无后效性:按照 Tarjan缩点,处理新图,拓扑排序中DAG动态规划/记忆化搜索 的顺序,来码题:

    #include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int head[N],to[N],tot,nxt[N],val[N],jb[N];
int Head[N],To[N],Tot,Nxt[N],f[N];
int Time,dfn[N],low[N],Belong[N],size[N],SCC,In[N];
stack<int> st;
queue<int> Q;
bool Instack[N];
void add_edge(int u,int v) {
tot++; nxt[tot]=head[u]; to[tot]=v; head[u]=tot;
}
void Add_edge(int u,int v) {
Tot++; Nxt[Tot]=Head[u]; To[Tot]=v; Head[u]=Tot;
}
void Tarjan(int u) {
dfn[u]=low[u]=++Time;
st.push(u); Instack[u]=true;
for(int i=head[u];i;i=nxt[i]) {
int v=to[i];
if(!dfn[v]) {
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(Instack[v]) {
low[u]=min(low[u],dfn[v]);
}
}
if(dfn[u]==low[u]) {
SCC++; int v;
do {
v=st.top(); st.pop(); Instack[v]=false;
Belong[v]=SCC; size[SCC]+=val[v];
}while(v!=u);
}
} int main() {
int n,m,s,p;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) {
int u,v;
scanf("%d%d",&u,&v);
add_edge(u,v);
}
for(int i=1;i<=n;i++) scanf("%d",&val[i]);
scanf("%d%d",&s,&p);
for(int i=1;i<=p;i++) scanf("%d",&jb[i]);
Tarjan(s); //这样被缩的点(有belong的点)都能被s到达
for(int u=1;u<=n;u++) {
for(int j=head[u];j;j=nxt[j]) {
int v=to[j];
if(!Belong[u]||!Belong[v]) continue;
if(Belong[u]!=Belong[v]) {
Add_edge(Belong[u],Belong[v]); In[Belong[v]]++;
}
}
}
Q.push(Belong[s]);
for(int i=1;i<=SCC;i++) {
f[i]=size[i];
}
while(!Q.empty()) {
int u=Q.front(); Q.pop();
for(int j=Head[u];j;j=Nxt[j]) {
int v=To[j];
f[v]=max(f[v],f[u]+size[v]);
In[v]--; if(!In[v]) Q.push(v);
}
}
int ans=0;
for(int i=1;i<=p;i++) {
if(Belong[jb[i]]) {
ans=max(ans,f[Belong[jb[i]]]);
}
}
printf("%d",ans);
return 0;
}

再讲一道有趣的强连通分量题目吧。

洛谷 P1407 稳定婚姻

题目大概讲了2n个点,其中有n对夫妻,又有m对情侣,请问能否在第i对夫妻离婚的情况下,所有人都能找到对象。

思路:将每对夫妻(女->男)建有向图,再将每对情侣按照(男->女)连边,【环的点数都为偶】 判断每队夫妻是否在同一个一个强连通分量上,这样它们不在一起时,才能错序排列以配偶。

因此,代码:

    #include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int a[N],b[N];
int num,tot,ng,nb,head[N],nxt[N],to[N],cnt;
int Time,dfn[N],low[N],Belong[N],SCC;
bool Instack[N];
stack<int> st;
void add_edge(int u,int v) {
tot++; nxt[tot]=head[u]; to[tot]=v; head[u]=tot;
}
void Tarjan(int u) {
dfn[u]=low[u]=++Time;
st.push(u); Instack[u]=true;
for(int i=head[u];i;i=nxt[i]) {
int v=to[i];
if(!dfn[v]) {
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(Instack[v]) {
low[u]=min(low[u],dfn[v]);
}
}
if(dfn[u]==low[u]) {
SCC++; int v;
do {
v=st.top(); st.pop(); Instack[v]=false;
Belong[v]=SCC;
}while(v!=u);
}
}
map<string,int> mp;
int main() {
int n,m;
scanf("%d",&n);
for(int i=1;i<=n;i++) {
string p,q;
cin>>p>>q;
mp[p]=++num;
mp[q]=++num;
a[i]=mp[p]; b[i]=mp[q];
add_edge(a[i],b[i]);
}
scanf("%d",&m);
for(int i=1;i<=m;i++) {
string p,q;
cin>>p>>q;
int u=mp[p],v=mp[q];
add_edge(v,u);
}
for(int i=1;i<=n*2;i++) {
if(!dfn[i]) {
Tarjan(i);
}
}
for(int i=1;i<=n;i++) {
if(Belong[a[i]]!=Belong[b[i]]) {
printf("Safe\n");
}
else printf("Unsafe\n");
}
return 0;
}

最新文章

  1. CSS的常见属性
  2. C++模板&amp;泛型编程
  3. Why is processing a sorted array faster than an unsorted array?
  4. 在MFC中添加用户自定义消息
  5. oracle db打one-off-patch 一例
  6. ZOJ1204——Additive equations(DFS)
  7. SpringInAction读书笔记--第2章装配Bean
  8. c#字符串驻留机制
  9. 微信支付[v3]
  10. Mac下将ISO写入U盘镜像
  11. Luogu P3390 【模板】矩阵快速幂
  12. ldap数据库--ODSEE--安装
  13. 【转载】C#.NET WebApi返回各种类型(图片/json数据/字符串),.net图片转二进制流或byte
  14. iOS UILabel 文字 置顶/置底 实现
  15. 一直觉得用java很不顺心
  16. python第九十六天 ---Django(1)
  17. AISing Programming Contest 2019 翻车记
  18. MySQL视图已经授权,但是无法访问
  19. SD卡镜像烧写--树莓派为例
  20. common常用到的类

热门文章

  1. python爬取梦幻西游召唤兽资质信息(不包含变异)
  2. 搭建 LNMP 环境
  3. 物理层(PHY)
  4. C++怎么实现多态?
  5. SpringMVC 解析(五)URI链接处理
  6. 都2022年了,HDFS为何还如此能战!
  7. Codeforces Round #754 (Div. 2), problem: (A) A.M. Deviation泪目 万万没想到狂wa是因为这
  8. SpringCloud微服务实战——搭建企业级开发框架(三十九):使用Redis分布式锁(Redisson)+自定义注解+AOP实现微服务重复请求控制
  9. 2021.12.10 P5041 [HAOI2009]求回文串(树状数组求逆序对)
  10. ps、top命令查找不到进程的解决方案