关于连通性问题的Tarjan算法暂结
关于基础知识的预备桥和割点、双联通分量、强连通分量,支配树。(并不会支配树)
关于有向图的Tarjan,是在熟悉不过的了,它的主要功能就是求强联通分量,缩个点,但是要注意一下构建新图的时候有可能出现重边(即使原图没有重边),他还时常和拓扑排序放在一起。eg:
#include<cstdio>
#include<cstring>
#include<algorithm>
char xB[(<<)+],*xS=xB,*xT=xB;
#define gtc (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++)
inline void read(int &x){
register char ch=gtc;
for(x=;ch<''||ch>'';ch=gtc);
for(;ch>=''&&ch<='';x=(x<<)+(x<<)+ch-'',ch=gtc);
}
const int N=;
const int M=;
struct V{int to,next;}c[M<<];
int head[N],e[N],t;
inline void add(int x,int y,int *id){
c[++t].to=y,c[t].next=id[x],id[x]=t;
}
int dfn[N],low[N],Ti,belong[N],stack[N],top,num,size[N];
bool in[N];
int n,m;
int f[N],sum[N],degree[N];
int q[N],front=,tail;
int ans,fin,P;
int visit[N];
inline void Tarjan(int x){
dfn[x]=low[x]=++Ti,in[x]=true,stack[++top]=x;
for(int i=head[x];i;i=c[i].next)
if(dfn[c[i].to]==)
Tarjan(c[i].to),low[x]=std::min(low[x],low[c[i].to]);
else if(in[c[i].to])
low[x]=std::min(low[x],dfn[c[i].to]);
if(low[x]==dfn[x]){
int j;++num;
do{
j=stack[top--],in[j]=false,belong[j]=num,++size[num];
}while(j!=x);
}
}
int main(){
read(n),read(m),read(P);
for(int i=,x,y;i<=m;++i)
read(x),read(y),add(x,y,head);
for(int i=;i<=n;++i)
if(dfn[i]==)Tarjan(i);
for(int x=;x<=n;++x)
for(int i=head[x];i;i=c[i].next)
if(belong[x]!=belong[c[i].to])
add(belong[x],belong[c[i].to],e),++degree[belong[c[i].to]];
for(int i=;i<=num;++i){
if(degree[i]==)q[++tail]=i;
sum[i]=;
}
while(front<=tail){
int x=q[front++];
f[x]+=size[x];
if(f[x]>ans)ans=f[x],fin=sum[x];
else if(f[x]==ans)fin=(fin+sum[x])%P;
for(int i=e[x];i;i=c[i].next){
--degree[c[i].to];
if(visit[c[i].to]!=x){
if(f[x]>f[c[i].to])f[c[i].to]=f[x],sum[c[i].to]=sum[x];
else if(f[x]==f[c[i].to])sum[c[i].to]=(sum[c[i].to]+sum[x])%P;
visit[c[i].to]=x;
}
if(degree[c[i].to]==)q[++tail]=c[i].to;
}
}
printf("%d\n%d",ans,fin);
return ;
}
【BZOJ 1093】[ZJOI2007]最大半连通子图
(在有向图的Tarjan里面还有支配树,表示这个坑过了联赛再补。)
对于无向图的Tarjan就广泛得多了,首先桥和边双,以及割点和点双。
关于桥就是他的防重边打法(就是记录父亲边而不是父亲,这样效率不仅高而且还防重边)。eg:
#include<cstdio>
#include<cstring>
#include<algorithm>
char xB[(<<)+],*xS=xB,*xT=xB;
#define gtc (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++)
inline void read(int &sum){
register char ch=gtc;
for(sum=;ch<''||ch>'';ch=gtc);
for(;ch>=''&&ch<='';sum=(sum<<)+(sum<<)+ch-'',ch=gtc);
}
const int N=;
struct V{int to,next,id;}c[N<<];
int head[N],t;
inline void add(int x,int y,int z){
c[++t].to=y,c[t].next=head[x],c[t].id=z,head[x]=t;
}
int soa[N],sob[N];
int dfn[N],low[N],Ti;
bool is[N];
int n,m,k,l,s;
inline void Tarjan(int x,int OPai){
dfn[x]=low[x]=++Ti;
for(int i=head[x];i;i=c[i].next)
if(dfn[c[i].to]==)
Tarjan(c[i].to,c[i].id),
soa[x]+=soa[c[i].to],sob[x]+=sob[c[i].to],
low[x]=std::min(low[x],low[c[i].to]);
else if(c[i].id!=OPai)
low[x]=std::min(low[x],dfn[c[i].to]);
if(low[x]==dfn[x]&&(soa[x]==||sob[x]==||soa[x]==k||sob[x]==l))
is[OPai]=true,++s;
}
int main(){
//freopen("read.in","r",stdin);
read(n),read(m),read(k),read(l);
for(int i=,x;i<=k;++i)
read(x),soa[x]=;
for(int i=,x;i<=l;++i)
read(x),sob[x]=;
for(int i=,x,y;i<=m;++i)
read(x),read(y),add(x,y,i),add(y,x,i);
Tarjan(,);
printf("%d\n",s-);
for(int i=;i<=m;++i)
if(is[i])
printf("%d\n",i);
return ;
}
【vijos 1769】网络的关键边
对于边双我并没有打过题,有一种是记录栈然后跳栈(并不知道怎么打),但是有一种更好打的就是先求桥并记录然后在dfs一遍求各个边双。
然后就是割点了,这个比较好求,在这里主要是体现了对于dfs树的利用,这就涉及到Tarjan算法本身原理了,这里就有一道dfs树上利用割点的树归:
#include <cstdio>
#include <cstring>
#include <algorithm>
typedef long long LL;
const int N=;
const int M=;
char xB[(<<)+],*xS=xB,*xTT=xB;
#define gtc() (xS==xTT&&(xTT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xTT)?0:*xS++)
inline void read(int &sum){
register char ch=gtc();
for(sum=;ch<''||ch>'';ch=gtc());
for(;ch>=''&&ch<='';sum=(sum<<)+(sum<<)+ch-'',ch=gtc());
}
struct V{int to,next;}c[M<<];
int head[N],t;
inline void add(int x,int y){
c[++t].to=y,c[t].next=head[x],head[x]=t;
}
int dfn[N],low[N],size[N],Ti;
LL ans[N];
int n,m;
inline void Tarjan(int x){
dfn[x]=low[x]=++Ti,size[x]=;
for(int i=head[x],cnt=n-;i;i=c[i].next)
if(!dfn[c[i].to]){
Tarjan(c[i].to),size[x]+=size[c[i].to];
low[x]=std::min(low[x],low[c[i].to]);
if(low[c[i].to]>=dfn[x])
cnt-=size[c[i].to],ans[x]+=(LL)size[c[i].to]*cnt*;
}else low[x]=std::min(low[x],dfn[c[i].to]);
}
int main(){
read(n),read(m);
for(int i=,x,y;i<=m;++i)
read(x),read(y),add(x,y),add(y,x);
for(int i=,temp=(n-)<<;i<=n;++i)
ans[i]=temp;
Tarjan();
for(int i=;i<=n;++i)printf("%lld\n",ans[i]);
return ;
}
【BZOJ 1123】[POI2008]BLO
还有一个板子题:
#include<cstdio>
#include<cstring>
#include<algorithm>
const int N=;
char xB[(<<)+],*xS=xB,*xTT=xB;
#define gtc() (xS==xTT&&(xTT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xTT)?0:*xS++)
inline void read(int &sum){
register char ch=gtc();
for(sum=;ch<''||ch>'';ch=gtc());
for(;ch>=''&&ch<='';sum=(sum<<)+(sum<<)+ch-'',ch=gtc());
}
struct V{int to,next;}c[N<<];
int head[N],t;
inline void add(int x,int y){
c[++t].to=y,c[t].next=head[x],head[x]=t;
}
int low[N],dfn[N],Ti;
bool is[N];
int degree[N];
int n,m;
int ans[N];
inline void Tarjan(int x){
low[x]=dfn[x]=++Ti;int cnt=;
for(int i=head[x];i;i=c[i].next)
if(dfn[c[i].to]==){
Tarjan(c[i].to);
low[x]=std::min(low[x],low[c[i].to]);
if(low[c[i].to]>=dfn[x])++cnt;
}else low[x]=std::min(low[x],dfn[c[i].to]);
if((x==&&cnt>=)||(x!=&&cnt>=))is[x]=true;
}
int main(){
read(n),read(m);
for(int i=,x,y;i<=m;++i){
read(x),read(y);
add(x,y),add(y,x);
++degree[x],++degree[y];
}
Tarjan();
for(int i=;i<=n;++i)
if(is[i]==false&&n-+degree[i]==m)
ans[++ans[]]=i;
printf("%d\n",ans[]);
for(int i=;i<=ans[];++i)
printf("%d ",ans[i]);
return ;
}
UOJ #67. 新年的毒瘤
关于点双,网上大多数人都说他必须栈内存边,他们只关注于一个点会存在于多个点双,并没有去解决他,但是我想说可以存点!!!
首先我们先证明一件事,就是一个点双内的点在dfs树上一定是一段连续的简单路径。(我们只考虑连通图)证明:第一,对于一个无向连通图,如果把他的点双变成点原来的点只留下割点并保持原来的连通性,那么这张图一定是一棵树,那么在各个点双内部一定是这棵dfs树的一部分其内部与外界只有割点保持联通,第二,对于一个点双内部的点与树边所形成的结构不可能出现叉型结构,如果出现叉,首先这个叉的分支一定有一个是来源,其他是回溯产物,而叉至少有三个分支,所以回溯产物至少有两个,但是其中一个在回到原点之前一定会访问另外的分支,因此不成立,综上,一个点双内的点在dfs树上一定是一段连续的简单路径。
那么我们现在就是有一群简单路径,如果我们将点入栈,出栈时一直pop到本节点,那么就会出现一系列的错误,因为栈里的点不全是本点双内的点,但是如果我们pop到这次连向的点并另外加入本点的话,就不会出现这样的问题,因为首先我们递归下去,最小的枝杈(就是他只有深度最小的点与其他点双有联系)一定会被完整pop如此递归下去所有的点都会去他应在的点双。
这样的作法我已经A了三道题,有一道是一道NOIP模拟题就不在这里呈现。
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
char xB[(<<)+],*xS=xB,*xT=xB;
#define gtc (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++)
inline void read(int &sum){
register char ch=gtc;
for(sum=;ch<''||ch>'';ch=gtc);
for(;ch>=''&&ch<='';sum=(sum<<)+(sum<<)+ch-'',ch=gtc);
}
const int N=;
const int M=;
int ans[N],Ti;
int dis[M],num;
int stack[N],top;
int f[M],ote[M];
inline int find(int x){
return f[x]==x?x:(f[x]=find(f[x]));
}
int dfn[N],low[N];
struct V{int to,next;}c[M<<];
int head[M],q[M],e[N],t;
inline void add(int x,int y,int *id){
c[++t].to=y,c[t].next=id[x],id[x]=t;
}
bool v[M];
int n,m,Q,rt;
inline void Tarjan(int x){
int cnt=;
low[x]=dfn[x]=++Ti,stack[++top]=x;
for(int i=e[x];i;i=c[i].next)
if(dfn[c[i].to]==){
Tarjan(c[i].to);
low[x]=std::min(low[x],low[c[i].to]);
if(low[c[i].to]>=dfn[x]){
int j;++num;
do{
j=stack[top--],add(j,num+n,head),add(num+n,j,head);
}while(j!=c[i].to);
add(x,num+n,head),add(num+n,x,head);
++cnt;
}
}else low[x]=std::min(low[x],dfn[c[i].to]);
if(x==){
if(cnt>=)rt=;
else rt=num;
}
}
inline void Tarjan(int x,int fa){
f[x]=x,ote[x]=fa;
for(int i=head[x];i;i=c[i].next)
if(c[i].to!=fa)
Tarjan(c[i].to,x),f[c[i].to]=x;
v[x]=true;
for(int i=q[x];i;i=c[i].next)
if(v[c[i].to])
--dis[find(c[i].to)],--dis[ote[find(c[i].to)]];
}
inline void dfs(int x,int fa){
for(int i=head[x];i;i=c[i].next)
if(c[i].to!=fa)
dfs(c[i].to,x),dis[x]+=dis[c[i].to];
if(x<=n)ans[x]=dis[x];
}
int main(){
//freopen("read.in","r",stdin);
read(n),read(m),read(Q);
for(int i=,x,y;i<=m;++i)
read(x),read(y),add(x,y,e),add(y,x,e);
Tarjan();
for(int i=,x,y;i<=Q;++i)
read(x),read(y),add(x,y,q),add(y,x,q),++dis[x],++dis[y];
Tarjan(rt,);
dfs(rt,);
for(int i=;i<=n;++i)
printf("%d\n",ans[i]);
return ;
}
【BZOJ 3331】[BeiJing2013]压力
#include<set>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ft first
#define sd second
#define pb push_back
#define mmp(a,b) (std::make_pair(a,b))
#define mid ((l+r)>>1)
#define newnode (node+(sz++))
char xB[(<<)+],*xS=xB,*xTT=xB;
#define gtc (xS==xTT&&(xTT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xTT)?0:*xS++)
inline void read(int &sum){
register char ch=gtc;
for(sum=;ch<''||ch>'';ch=gtc);
for(;ch>=''&&ch<='';sum=(sum<<)+(sum<<)+ch-'',ch=gtc);
}
typedef std::pair<int,int> pii;
const int N=;
const int Inf=0x3f3f3f3f;
std::set<pii> st[N];
int dfn[N],low[N],stack[N],top,Ti,min[N],num;
bool is[N];
int size[N<<],height[N<<],f[N<<],anc[N<<],deep[N<<],id[N<<],anti[N<<];
int len;
int n,m,q;
int val[N<<];
struct V{int to,next;}c[N<<];
int head[N],t,to[N<<];
std::vector<int> mem;
inline void add(int x,int y,int *name){
c[++t].to=y,c[t].next=name[x],name[x]=t;
}
inline void Tarjan(int x){
dfn[x]=low[x]=++Ti,stack[++top]=x;
for(int i=head[x];i;i=c[i].next)
if(dfn[c[i].to]==){
deep[c[i].to]=deep[x]+;
Tarjan(c[i].to),low[x]=std::min(low[x],low[c[i].to]);
if(low[c[i].to]>=dfn[x]){
int j;
min[++num]=x;
mem.clear(),mem.pb(x);
do{
j=stack[top--],mem.pb(j);
if(deep[j]<deep[min[num]])
min[num]=j;
}while(j!=c[i].to);
for(j=;j<mem.size();++j)
if(mem[j]==min[num])
add(min[num],num+n,to),f[num+n]=min[num];
else
add(num+n,mem[j],to),f[mem[j]]=num+n,st[num].insert(mmp(val[mem[j]],mem[j]));
val[num+n]=st[num].begin()->ft;
}
}else low[x]=std::min(low[x],dfn[c[i].to]);
}
inline void dfs(int x){
size[x]=,deep[x]=deep[f[x]]+;
for(int i=to[x];i;i=c[i].next){
dfs(c[i].to);
size[x]+=size[c[i].to];
if(size[c[i].to]>size[height[x]])
height[x]=c[i].to;
}
}
inline void dfs(int x,int tp){
id[x]=++len,anti[len]=x,anc[x]=tp;
if(height[x])dfs(height[x],tp);
for(int i=to[x];i;i=c[i].next)
if(c[i].to!=height[x])
dfs(c[i].to,c[i].to);
}
struct Segment_Tree{
Segment_Tree *ch[];
int min;
}*root,node[N<<];
int sz;
inline void build(Segment_Tree *&p,int l,int r){
p=newnode;
if(l==r){
p->min=val[anti[l]];
return;
}
build(p->ch[],l,mid);
build(p->ch[],mid+,r);
p->min=std::min(p->ch[]->min,p->ch[]->min);
}
inline void U(Segment_Tree *p,int l,int r,int pos,int key){
if(l==r){p->min=key;return;}
if(pos<=mid)U(p->ch[],l,mid,pos,key);
else U(p->ch[],mid+,r,pos,key);
p->min=std::min(p->ch[]->min,p->ch[]->min);
}
inline int Q(Segment_Tree *p,int l,int r,int z,int y){
if(z<=l&&r<=y)return p->min;
int ret=Inf;
if(z<=mid)ret=std::min(ret,Q(p->ch[],l,mid,z,y));
if(mid<y)ret=std::min(ret,Q(p->ch[],mid+,r,z,y));
return ret;
}
inline int Q(int x,int y){
int ret=Inf;
while(anc[x]!=anc[y]){
if(deep[anc[x]]<deep[anc[y]])std::swap(x,y);
ret=std::min(ret,Q(root,,len,id[anc[x]],id[x]));
x=f[anc[x]];
}
if(deep[x]<deep[y])std::swap(x,y);
ret=std::min(ret,Q(root,,len,id[y],id[x]));
if(y>n)ret=std::min(ret,val[f[y]]);
return ret;
}
inline bool check(){
register char ch=gtc;
while(ch!='C'&&ch!='A')ch=gtc;
return ch=='C';
}
int main(){
read(n),read(m),read(q);
for(int i=;i<=n;++i)read(val[i]);
for(int i=,x,y;i<=m;++i)
read(x),read(y),add(x,y,head),add(y,x,head);
Tarjan(),dfs(),dfs(,),build(root,,len);
for(int i=,x,y;i<=q;++i){
if(check()){
read(x),read(y);
U(root,,len,id[x],y);
if(x==){val[x]=y;continue;}
st[f[x]-n].erase(mmp(val[x],x));
st[f[x]-n].insert(mmp(y,x));
val[f[x]]=st[f[x]-n].begin()->ft;
U(root,,len,id[f[x]],val[f[x]]);
val[x]=y;
}else{
read(x),read(y);
printf("%d\n",Q(x,y));
}
}
}
codeforces Round #278 E Tourists
这两道题反映出一个点双建图的一般思路,就是对一个点双建一个点,这个点双内部深度最小的点当做其父亲其余为其儿子,这样的话在新树上的两点之间的路径会经过他们之间的点双和割点,而且有的时候要特殊处理lca节点。
在真正打点双之前我一直觉得建图方法是“把他的点双变成点原来的点只留下割点并保持原来的连通性”,我不知道这样建图会不会有一天排上用场,只是目前的题目都用上面的做法解决得十分顺手。
关于连通性问题的Tarjan算法暂结的更多相关文章
- tarjan算法与无向图的连通性(割点,桥,双连通分量,缩点)
基本概念 给定无向连通图G = (V, E)割点:对于x∈V,从图中删去节点x以及所有与x关联的边之后,G分裂为两个或两个以上不相连的子图,则称x为割点割边(桥)若对于e∈E,从图中删去边e之后,G分 ...
- 有向图强连通分支的Tarjan算法讲解 + HDU 1269 连通图 Tarjan 结题报告
题目很简单就拿着这道题简单说说 有向图强连通分支的Tarjan算法 有向图强连通分支的Tarjan算法伪代码如下:void Tarjan(u) {dfn[u]=low[u]=++index//进行DF ...
- 图的连通性——Tarjan算法&割边&割点
tarjan算法 原理: 我们考虑 DFS 搜索树与强连通分量之间的关系. 如果结点 是某个强连通分量在搜索树中遇到的第⼀个结点,那么这个强连通分量的其余结点肯定 是在搜索树中以 为根的⼦树中. 被称 ...
- 图的连通性--Tarjan算法
一些概念 无向图: 连通图:在无向图中,任意两点都直接或间接连通,则称该图为连通图.(或者说:任意两点之间都存在可到达的路径) 连通分量: G的 最大连通子图 称为G的连通分量. 有向图 (ps.区别 ...
- 强连通分量的Tarjan算法
资料参考 Tarjan算法寻找有向图的强连通分量 基于强联通的tarjan算法详解 有向图强连通分量的Tarjan算法 处理SCC(强连通分量问题)的Tarjan算法 强连通分量的三种算法分析 Tar ...
- Tarjan算法:求解图的割点与桥(割边)
简介: 割边和割点的定义仅限于无向图中.我们可以通过定义以蛮力方式求解出无向图的所有割点和割边,但这样的求解方式效率低.Tarjan提出了一种快速求解的方式,通过一次DFS就求解出图中所有的割点和割边 ...
- 『追捕盗贼 Tarjan算法』
追捕盗贼(COCI2007) Description 为了帮助警察抓住在逃的罪犯,你发明了一个新的计算机系统.警察控制的区域有N个城市,城市之间有E条双向边连接,城市编号为1到N. 警察经常想在罪犯从 ...
- 『Tarjan算法 无向图的双联通分量』
无向图的双连通分量 定义:若一张无向连通图不存在割点,则称它为"点双连通图".若一张无向连通图不存在割边,则称它为"边双连通图". 无向图图的极大点双连通子图被 ...
- 浅谈Tarjan算法
从这里开始 预备知识 两个数组 Tarjan 算法的应用 求割点和割边 求点-双连通分量 求边-双连通分量 求强连通分量 预备知识 设无向图$G_{0} = (V_{0}, E_{0})$,其中$V_ ...
随机推荐
- 生産管理(PP)
伝票系 製造指図 マスタ系 生産資源/治工具 作業区 能力 作業手順 作業バージョン 作業記録 需要予測プロファイル 計画手配 MRP レシピ その他 カスタマイズ系 BOM関連 製造指図確認 伝票系 ...
- C#属性默认值设置
关于在MVC中view中设置默认值,可以象如下设置: 1.关于VIEWMODEL的部分 如果是C# 6.0,网上资料查到说可以 如果语法不支持,只能改回.net 2.0的写法. public cla ...
- 白话HMM系列1——从一个缩略语还原的例子说起
HMM到底是一个什么样的东西,我想从我研究的一个应用场景开始说起.之所以想重新描述一下我对HMM的理解,是因为上次面试百度糯米的时候,自己没有把HMM在应用上说的很明白,不过糯米的那位郑姓面试官我也是 ...
- 【数据库】 SQL SERVER 2014 实用新特性
[数据库] SQL SERVER 2014 实用新特性 官方链接 一. 内存优化表 大幅提高数据库性能,不过目前没有窗口化设计只能写语句 二. 索引增强
- 【JS笔记】闭包
首先看执行环境和作用域的概念.执行环境定义了变量或函数有权访问的其他数据,决定它们的行为,每个执行环境都有一个与其关联的变量对象,保存执行环境中定义的变量.当代码在一个环境中执行时,会创建变量对象的一 ...
- 「暑期训练」「基础DP」 Monkey and Banana (HDU-1069)
题意与分析 给定立方体(个数不限),求最多能堆叠(堆叠要求上方的方块严格小于下方方块)的高度. 表面上个数不限,问题是堆叠的要求决定了每个方块最多可以使用三次.然后就是对3n" role=& ...
- 每天一个Linux命令(14):dpkg命令
dpkg命令是Debian Linux系统用来安装.创建和管理软件包的实用工具. 语法: dpkg (选项) (参数) 选项: -i:安装软件包: -r:删除软件包: -P:删除软件包的同时删除其配置 ...
- fiddler之弱网测试
今天就说一下如何使用fiddler做弱网测试 1.首先要把手机的代理打开,这就不多讲了哈,不懂得话请点传送门:https://www.cnblogs.com/fuxinxin/p/9146693.ht ...
- HDFS伪分布式环境搭建
(一).HDFS shell操作 以上已经介绍了如何搭建伪分布式的Hadoop,既然环境已经搭建起来了,那要怎么去操作呢?这就是本节将要介绍的内容: HDFS自带有一些shell命令,通过这些命令我们 ...
- 剑指offer-二进制中1的个数11
题目描述 输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示. class Solution: def NumberOf1(self, n): # write code here coun ...