有向图: 割点:删掉不连通 割边(桥):删掉不连通 连通分量:极大子图 点双连通图:删任意一点仍连通,即 没有割点 边双连通图:删任意一边仍连通,即 没有桥 点双连通分量:极大点双连通子图 边双连通分量:极大边双连通子图 (单点也是边双连通分量)
边双连通:删桥后,双方都是边双
有向图: 弱连通:单向到达 强连通:双向到达(单点也是强连通) 强连通一般用来缩点
求割点和桥的代码(洛谷已过):
int cnt;
int dfn[N]; //表示第几个搜到i点
int low[N]; //表示i点及其子树通过返祖边连到的最小dfn值 //向下若干次,再通过返祖边向上跳一次 能 跳到的最高点
void dfs(int x){
dfn[x]=low[x]=++cnt;
int son=0;
for(int i=head[x];i;i=e[i].next){
int y=e[i].a;
if(!dfn[y]){ //若y没有被访问过,则其为一个新点
son++; //son放外面
dfs(y);
low(x)=min(low[x],low(y));
if(low[y]>=dfn(x)){ //y能到达的最高祖点 比 x矮(或就是x) //求桥的话,把等于号去掉即可
//son++; //son放外面更好,因为son是用来判断根是否是割点的,而根的儿子y一定不能追溯至根以上,所以不需要放if里,且放外面刚好理解
if(x!=root || son>=2) vis[x]=1; //vis为记录割点 //x不是根时,y没有返祖至x以上,则x为割点;fg>=2表示根至少要两个子树才能是割点
}
}
else low[x]=min(low[x],dfn[y]); //若y已被访问过,则y就是祖点,此边为返祖边
}
}
以下是kuangbin的模板(一定正确)(原来雨巨是用kuangbin的板子呀,以后我也用) 割点(割点用以下的板子,别用上面的了):
int cnt;
int dfn[N],low[N],vis[N];
void dfs(int x,int fa){
dfn[x]=low[x]=++cnt;
int son=0;
for(int i=head[x];i;i=e[i].next){
int y=e[i].a;
if(y==fa) continue;
if(!dfn[y]){
son++;
dfs(y,x);
low[x]=min(low[x],low[y]);
/*两种判断割点条件的写法,以后用第二种
第一种【第一种和雨巨的神似】:*/
/*if(dfn[x]<=low[y]){
if(x!=fa || son>1) vis[x]=1;
} */
if(x!=fa&&dfn[x]<=low[y]) vis[x]=1; //第二种写法,用此写法,下面记得特判根 //x!=fa表示非根
}
else if(y!=fa) low[x]=min(low[x],dfn[y]); //这里的if条件可以省略,因为y==fa时 continue了
}
if(x==fa&&son>=2) vis[x]=1;
}
求强连通分量的代码: 强连通分量:任意两点可以互达,若边权为0,则可将整个强连通分量缩点
int cnt;
int dfn[N],low[N];
stack<int>s;
void dfs(int x){
dfn[x]=low[x]=++cnt;
s.push(x);
vis[x]=1; //记录x是否在栈中,且记录是否被访问过
for(int i=head[x];i;i=e[i].next){
int y=e[i].a;
if(!dfn[y]){ //判断y是否被访问过
dfs(y);
low[x]=min(low[x],low[y]);
}
else if(vis[y]) low[x]=min(low[x],dfn[y]); //else表示y被访问过,vis[y]=1表示y被访问过【即y为祖点】且还在栈中【即没有作为强连通分量的一部分弹出。y就是强连通分量的一部分,单点也是强分】,那么此处就有(一个)环【即强连通分量】
//以上if和else 都不满足,则表示被访问过且不在栈中,即 被作为强连通分量的一部分弹出了
}
if(dfn[x]==low[x]){ //dfn==low说明下面没有返祖到x以上的点,则要么返到x以下的点成环,要么返祖返到x成环,所以x为强连通分量的最高点 //单点也属于强连通分量
ans++; //强连通分量的数量++
do{
int t=s.top();s.pop();
v[t]=0;
num[t]=ans; //num[i]表示i为哪一个强连通分量
} while(x!=t);
}
}