Tarjan 总结
Tarjan 基础
dfn[i]: 在dfs中该节点被搜索的次序(时间戳)。
low[i]: 为i或i的子树能够追溯到的最早的栈中节点的次序号。
当 dfn[i] == low[i] 时,为i或i的子树可以构成一个强连通分量。
void tarjan(int x)
{
id++;
dfn[x] = id;
low[x] = id;
vis[x] = ;//是否在栈中
stk[++top] = x;//入栈
for(int i = head[x]; i != ; i = edge[i].nxt){
int temp = edge[i].to;
if(!dfn[temp]){
tarjan(temp);
low[x] = min(low[x],low[temp]);
}
else if(vis[temp]){
low[x] = min(low[x],dfn[temp]);
}
}
if(dfn[x] == low[x]){//构成强连通分量,进行染色
vis[x] = ;
color[x] = ++col;
while(stk[top] != x){
color[stk[top]] = col;
vis[stk[top--]] = ;
}
top--;
}
}
割边、割点
一、基本概念
桥:无向连通图中,如果删除某边后,图变成不连通,则称该边为桥。
割点:无向连通图中,如果删除某点后,图变成不连通,则称该点为割点。
二、Tarjan算法求解桥和割点
1.割点:1)当前节点为树根的时候,条件是要有至少两颗子树。
2)当前节点u不是树根的时候,条件是存在u的一个子节点v使得 low[v]>=dfn[u]。
2.桥:当且仅当无向边(u,v)是树枝边的时候,条件是 dfn[u]<low[v]。
#include<bits/stdc++.h> using namespace std;
const int N = ;
vector<int>G[N];
int n,m,low[N],dfn[N];
bool is_cut[N];
int father[N],tim;
void input()
{
scanf("%d%d",&n,&m);
int a,b;
for(int i=;i<=m;++i)
{
scanf("%d%d",&a,&b);
G[a].push_back(b);
G[b].push_back(a);
}
}
void Tarjan(int i,int Father)
{
father[i]=Father;
dfn[i]=low[i]=tim++;
for(int j=;j<G[i].size();++j)
{
int k=G[i][j];
if(dfn[k]==-)
{
Tarjan(k,i);
low[i]=min(low[i],low[k]);
}
else if(Father!=k)/*假如k是i的父亲的话,那么这就是无向边中的重边,有重边那么一定不是桥*/
low[i]=min(low[i],dfn[k]);
}
}
void _count()
{
int rootson=;
Tarjan(,);
for(int i=;i<=n;++i)
{
int v=father[i];
if(v==)
rootson++;
else{
if(low[i]>=dfn[v])/*割点的条件*/
is_cut[v]=true;
}
}
if(rootson>)
is_cut[]=true;
for(int i=;i<=n;++i)
if(is_cut[i])
printf("%d\n",i);
for(int i=;i<=n;++i)
{
int v=father[i];
if(v>&&low[i]>dfn[v])/*桥的条件*/
printf("%d,%d\n",v,i);
} }
int main()
{
input();
memset(dfn,-,sizeof(dfn));
memset(father,,sizeof(father));
memset(low,-,sizeof(low));
memset(is_cut,false,sizeof(is_cut));
_count();
return ;
}
有向图缩点
思想:将一个有向图强连通分量缩点为一个点去代替一堆点,要修改两个属性,一个是边,一个是点。
方法:运用Tarjan算法找出一个强连通分量,每次找出一个强连通分量,我们就用其中的一个点去代表这一堆点。记这个点为代表点( ̄□ ̄||只是自己这么叫),那么这堆点的contract[x] = 代表点。
对于非代表点:
①、将它的出边全部复制给代表点。
②、将它的点权加给代表点。
③、所有指向它的点在使用时用指向 contract[x] 代替即可,不需要做修改。
#include<bits/stdc++.h> using namespace std;
typedef long long ll;
const int maxn = ;
vector<int> e[maxn];
int ins[maxn], dfn[maxn], low[maxn], contract[maxn];
ll w[maxn];
int ind;
stack<int> s;
void tarjan(int u)
{
dfn[u] = low[u] = ++ind;
ins[u] = ;
s.push(u);
for(int i = ; i < e[u].size(); i++) {
int v = e[u][i];
if(!dfn[v])
{
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if(ins[v]) low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u]) {
int v;
do {
v = s.top();
s.pop();
ins[v] = ;
contract[v] = u;
if(u != v) {
w[u] += w[v];
while(!e[v].empty()) {
e[u].push_back(e[v].back());
e[v].pop_back();
}
}
} while(u != v);
}
} ll dfs(int u, ll cnt)
{
cnt += w[u];
ll ret = cnt;
for(int i = ; i < e[u].size(); i++) {
int v = contract[e[u][i]];
if(v != u) ret = max(ret, dfs(v, cnt));
}
return ret;
}
int main()
{
int n, m;
scanf("%d%d",&n,&m);
for(int i = ; i <= n; i++) {
scanf("%d",&w[i]);
}
for(int i = ; i <= m; i++) {
int u, v;
scanf("%d%d",&u,&v);
e[u].push_back(v);
}
tarjan();
ll ans = dfs(,);
printf("%lld\n",ans);
return ;
}
向图的双连通分量
一、点双连通分量
定义:对于一个连通图,如果任意两点至少存在两条点不重复路径,则称这个图为点双连通的(简称双连通)。点双连通图的定义等价于任意两条边都同在一个简单环中。对一个无向图,点双连通的极大子图称为点双连通分量(简称双连通分量)。
#include<bits/stdc++.h> using namespace std;
const int maxn = ; struct Edge {
int u,v;
Edge(int uu,int vv)
{
u = uu;
v = vv;
}
};
stack<Edge> s; struct edge //链式前向星建图的边结构
{
int v,next;
}edges[maxn]; int n,m; //节点的数目,无向边的数目
int e,head[maxn];
int dfn[maxn]; //第一次访问的时间戳
int dfs_clock; //时间戳
int iscut[maxn]; //标记节点是否为割点
int bcc_cnt; //点_双连通分量的数目
int bccno[maxn]; //节点属于的点_双连通分量的编号
vector<int> bcc[maxn]; //点_双连通分量 void addedges(int u,int v) //加边
{
edges[e].v = v;
edges[e].next = head[u];
head[u] = e++;
edges[e].v = u;
edges[e].next = head[v];
head[v] = e++;
} int dfs(int u,int fa)
{
int low = dfn[u] = ++dfs_clock;
int child = ;
for(int i=head[u];i!=-;i=edges[i].next)
{
int v = edges[i].v;
Edge e = (Edge){u,v};
if(!dfn[v])
{
s.push(e);
child++;
int lowv = dfs(v,u);
low = min(low,lowv); //用后代更新low
if(lowv >= dfn[u]) //找到了一个子树满足割顶的条件
{
iscut[u] = ;
bcc_cnt++;
bcc[bcc_cnt].clear();
for(;;) //保存bcc信息
{
Edge x = s.top(); s.pop();
if(bccno[x.u] != bcc_cnt) {bcc[bcc_cnt].push_back(x.u); bccno[x.u] = bcc_cnt;}
if(bccno[x.v] != bcc_cnt) {bcc[bcc_cnt].push_back(x.v); bccno[x.v] = bcc_cnt;}
if(x.u == u && x.v == v) break;
}
}
}
else if(dfn[v] < dfn[u] && v != fa) //用反向边更新low
{
s.push(e);
low = min(low,dfn[v]);
}
}
if(fa < && child == ) iscut[u] = ; //对于根节点若只有一个子树则不是割顶
return low;
} void init()
{
memset(dfn,,sizeof(dfn));
memset(iscut,,sizeof(iscut));
memset(head,-,sizeof(head));
memset(bccno,,sizeof(bccno));
e = ; dfs_clock = ; bcc_cnt = ;
} int main()
{
int u,v;
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
for(int i=;i<m;i++)
{
scanf("%d%d",&u,&v);
addedges(u,v);
}
dfs(,-);
for(int i=;i<=bcc_cnt;i++)
{
for(int j=;j<bcc[i].size();j++)
cout<<bcc[i][j]<<" ";
cout<<endl;
} }
return ;
}
二、边双连通分量
定义:对于一个连通图,如果任意两点至少存在两条边不重复路径,则称该图为边双连通的。边双连通图的定义等价于任意一条边至少在一个简单环中。对一个无向图,边双连通的极大子图称为边双连通分量。
#include<bits/stdc++.h> using namespace std; const int maxn = ;
struct Edge
{
int no,v,next; //no:边的编号
}edges[maxn]; int n,m,ebcnum; //节点数目,无向边的数目,边_双连通分量的数目
int e,head[maxn];
int dfn[maxn]; //第一次访问的时间戳
int dfs_clock; //时间戳
int isbridge[maxn]; //标记边是否为桥
vector<int> ebc[maxn]; //边_双连通分量 void addedges(int num,int u,int v) //加边
{
edges[e].no = num;
edges[e].v = v;
edges[e].next = head[u];
head[u] = e++;
edges[e].no = num++;
edges[e].v = u;
edges[e].next = head[v];
head[v] = e++;
} int dfs_findbridge(int u,int fa) //找出所有的桥
{
int lowu = dfn[u] = ++dfs_clock;
for(int i=head[u];i!=-;i=edges[i].next)
{
int v = edges[i].v;
if(!dfn[v])
{
int lowv = dfs_findbridge(v,u);
lowu = min(lowu,lowv);
if(lowv > dfn[u])
{
isbridge[edges[i].no] = ; //桥
}
}
else if(dfn[v] < dfn[u] && v != fa)
{
lowu = min(lowu,dfn[v]);
}
}
return lowu;
} void dfs_coutbridge(int u,int fa) //保存边_双连通分量的信息
{
ebc[ebcnum].push_back(u);
dfn[u] = ++dfs_clock;
for(int i=head[u];i!=-;i=edges[i].next)
{
int v = edges[i].v;
if(!isbridge[edges[i].no] && !dfn[v]) dfs_coutbridge(v,u);
}
} void init()
{
memset(dfn,,sizeof(dfn));
memset(isbridge,,sizeof(isbridge));
memset(head,-,sizeof(head));
e = ; ebcnum = ;
} int main()
{
int u,v;
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
for(int i=;i<m;i++)
{
scanf("%d%d",&u,&v);
addedges(i,u,v);
}
dfs_findbridge(,-);
memset(dfn,,sizeof(dfn));
for(int i=;i<=n;i++)
{
if(!dfn[i])
{
ebc[ebcnum].clear();
dfs_coutbridge(i,-);
ebcnum++;
}
}
for(int i=;i<ebcnum;i++)
{
for(int j=;j<ebc[i].size();j++)
cout<<ebc[i][j]<<" ";
cout<<endl;
}
}
return ;
}
三、点双连通分量和边双连通分量的区别和联系
①、二者都是基于无向图。
②、边双连通分量是删边后还连通,而后者是删点。
③、点双连通分量一定是边双连通分量(除两点一线的特殊情况),反之不一定。
④、点双连通分量可以有公共点,而边双连通分量不能有公共边。
Tarjan离线算法求LCA
思路:dfs...
#include<bits/stdc++.h> using namespace std;
const int N = ;
struct EDGE{
int next;
int to;
int lca;
};
EDGE edge[N];//树的链表
EDGE qedge[N];//需要查询LCA的两节点的链表
int n,m,p,x,y;
int num_edge,num_qedge,head[N],qhead[N];
int father[N];
int visit[N];//判断是否被找过
void add_edge(int from,int to){//建立树的链表
edge[++num_edge].next=head[from];
edge[num_edge].to=to;
head[from]=num_edge;
}
void add_qedge(int from,int to){//建立需要查询LCA的两节点的链表
qedge[++num_qedge].next=qhead[from];
qedge[num_qedge].to=to;
qhead[from]=num_qedge;
}
int fin(int z){//找爹函数
if(father[z]!=z)
father[z]=fin(father[z]);
return father[z];
}
int dfs(int x){//把整棵树的一部分看作以节点x为根节点的小树
father[x]=x;//由于节点x被看作是根节点,所以把x的father设为它自己
visit[x]=;//标记为已被搜索过
for(int k=head[x];k;k=edge[k].next)//遍历所有与x相连的节点
if(!visit[edge[k].to]){//若未被搜索
dfs(edge[k].to);//以该节点为根节点搞小树
father[edge[k].to]=x;//把x的孩子节点的father重新设为x
}
for(int k=qhead[x];k;k=qedge[k].next)//搜索包含节点x的所有询问
if(visit[qedge[k].to]){//如果另一节点已被搜索过
qedge[k].lca=fin(qedge[k].to);//把另一节点的祖先设为这两个节点的最近公共祖先
if(k%)//由于将每一组查询变为两组,所以2n-1和2n的结果是一样的
qedge[k+].lca=qedge[k].lca;
else
qedge[k-].lca=qedge[k].lca;
}
}
int main(){
scanf("%d%d%d",&n,&m,&p);//输入节点数,查询数和根节点
for(int i=;i<n;++i){
scanf("%d%d",&x,&y);//输入每条边
add_edge(x,y);
add_edge(y,x);
}
for(int i=;i<=m;++i){
scanf("%d%d",&x,&y);//输入每次查询,考虑(u,v)时若查找到u但v未被查找,所以将(u,v)(v,u)全部记录
add_qedge(x,y);
add_qedge(y,x);
}
dfs(p);//进入以p为根节点的树的深搜
for(int i=;i<=m;i++)
printf("%d ",qedge[i*].lca);//两者结果一样,只输出一组即可
return ;
}
Tarjan 总结的更多相关文章
- HDU4738 tarjan割边|割边、割点模板
题目:http://acm.hdu.edu.cn/showproblem.php?pid=4738 坑点: 处理重边 图可能不连通,要输出0 若求出的结果是0,则要输出1,因为最少要派一个人 #inc ...
- bzoj 1179[Apio2009]Atm (tarjan+spfa)
题目 输入 第一行包含两个整数N.M.N表示路口的个数,M表示道路条数.接下来M行,每行两个整数,这两个整数都在1到N之间,第i+1行的两个整数表示第i条道路的起点和终点的路口编号.接下来N行,每行一 ...
- tarjan讲解(用codevs1332(tarjan的裸题)讲解)
主要借助这道比较裸的题来讲一下tarjan这种算法 tarjan是一种求解有向图强连通分量的线性时间的算法.(用dfs来实现) 如果两个顶点可以相互通达,则称两个顶点强连通.如果有向图G的每两个顶点都 ...
- NOIP2009最优贸易[spfa变形|tarjan 缩点 DP]
题目描述 C 国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市.任意两个 城市之间最多只有一条道路直接相连.这 m 条道路中有一部分为单向通行的道路,一部分 为双向通行的道路 ...
- Tarjan
//求强连通分量 void uni(int x,int y){ if (rank[x]<rank[y]){ fa[x]=y; size[y]+=size[x]; }else{ rank[x]+= ...
- 【UOJ#67】新年的毒瘤 Tarjan 割点
#67. 新年的毒瘤 UOJ直接黏贴会炸... 还是戳这里吧: http://uoj.ac/problem/67#tab-statement Solution 看到这题的标签就进来看了一眼. 想 ...
- 【Codefoces487E/UOJ#30】Tourists Tarjan 点双连通分量 + 树链剖分
E. Tourists time limit per test: 2 seconds memory limit per test: 256 megabytes input: standard inpu ...
- 【BZOJ-1123】BLO Tarjan 点双连通分量
1123: [POI2008]BLO Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 970 Solved: 408[Submit][Status][ ...
- 【BZOJ-2730】矿场搭建 Tarjan 双连通分量
2730: [HNOI2012]矿场搭建 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1602 Solved: 751[Submit][Statu ...
- Tarjan三把刀
搞过OI的对tarjan这个人大概都不陌生.这个人发明了很多神奇的算法,在OI届广被采用. 他最广泛采用的三个算法都是和$dfn$,$low$相关的. 有向图求强连通分量 其实说直白点,就是缩点.用得 ...
随机推荐
- poj3614Sunscreen
Description To avoid unsightly burns while tanning, each of the C (1 ≤ C ≤ 2500) cows must cover her ...
- HDU 6583 Typewriter 题解
——本题来自杭电多校第一场 题意:给定一个字符串,主角需要用打字机将字符串打出来,每次可以: 1.花费p来打出任意一个字符 2.花费q来将已经打出的某一段(子串)复制到后面去 对于这种最优化的问题,我 ...
- mooc-IDEA 编写高质量代码--009
十五.IntelliJ IDEA -编写高质量代码 1.重构 [1]重构变量 选中某个变量,按住 shift+F6,修改变量名,则所有该变量名均会被重构为新变量名 [2]重构方法[ctrl+F6 | ...
- deb包转换为rpm包格式
在Debian系列中安装软件包可以使用apt或者dpkg安装deb包,但是在CentOs, Redhat等则只能安装RPM包,如果希望在Redhat或者CentOS下也安装Deb包的话是不可行的, 但 ...
- java的spi(Service Provider Interface)机制及源码(java8)
1.什么是java的spi spi 全称为 service provider interface 即 服务提供接口,用来作为服务的扩展发现.在运行时动态添加接口的实现,是对接口的实现类的创建管理. 2 ...
- P3826 [NOI2017]蔬菜
传送门 注意每一单位蔬菜的变质时间是固定的,不随销售发生变化 固定的...... 就是每一个单位的蔬菜在哪一天变质是早就定好了的 发现从第一天推到最后一天很不好搞 考虑反过来,从最后一天推到第一天,这 ...
- zabbix3.0自动发现磁盘并监控磁盘IO
Zabbix 版本:3.0 操作系统:Ubuntu16.04 操作环境,在被监控的主机上安装zabbix agent.安装方式为源码包安装. 简要安装步骤: 参考:https://www.zabbix ...
- 2. ZooKeeper基础
1. ZooKeeper的特性 ZooKeeper的特性主要从会话.数据节点,版本,Watcher,ACL权限控制,集群角色这些部分来了解,其中需要重点掌握的数据节点与Watcher 1.1 会话 客 ...
- 【JAVA】 01-Java基础知识
链接: 笔记目录:毕向东Java基础视频教程-笔记 GitHub库:JavaBXD33 测试 01-Java基础知识 一.数据类型 基本类型 包装类型 缓存池 二.String 概览 不可变的好处 S ...
- <一> idea+gradle+springboot创建项目
转载自https://windcoder.com/springbootchutan-chuangjianxiangmu 前言 一边学习公司用到的技术,一边重构小程序后端,从而更好的理解公司搭建的框架. ...