连通性相关

前置知识

一个结点 的 DFS 序是指深度优先搜索遍历时改结点被搜索的次序,简记为 dfn\(x\)

Tarjan 缩点维护两个东西,一个是 DFS 序,一个是 Low 值,开两个数组,dfn和low。

dfn:dfs序。

low:该节点能够追溯到的最小的dfn。

设当前枚举点为u ,连出去的边为v 。

  1. v未被访问:继续对 v进行深度搜索。在回溯过程中,用low\(v\) 更新low\(u\)。因为存在从u到v 的直接路径,所以v能够回溯到的已经在栈中的结点,u也一定能够回溯到。
  2. v被访问过,已经在栈中:即已经被访问过,根据 low值的定义,则用dfn\(v\)更新low\(u\)

在回溯的过程中,判定low\(u\)==dfn\(u\)的条件是否成立,如果成立,则从栈中取出一个点,处理它所在的强连通分量的编号以及大小,也可以处理其他的一些操作,这样直到把所有点处理完为止。

void tarjan(int x){
	low[x]=dfn[x]=++tot;
	sta[++sc]=x;vis[x]=1;
	for(int i=head[x];i;i=a[i].next ){
		int now=a[i].v ;
		if(!dfn[now])tarjan(now),low[x]=min(low[x],low[now]);
		else if(vis[now])
			low[x]=min(low[x],dfn[now]);
	}
	if(low[x]==dfn[x]){
		int Top=sta[sc--];vis[Top]=0;(Num[Top]=++t;……根据题目添加
		while(Top!=x){
			Top=sta[sc--];
			vis[Top]=0;
			Num[Top]=t;
		}
	}
}

简写版:

dfn[u]=low[u]=++idx,vis[u]=true;stc[++sc]=u;
for(int i=head[u];i;i=e[i].nxt){
	int v=e[i].v;
	if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
	else if(vis[v]) low[u]=min(dfn[v],low[u]);
}
	if(dfn[u]==low[u]){
	int Top=stc[sc--];vis[Top]=0,……;
	while(Top!=u) Top=stc[sc--],vis[Top]=0,……;
}

割点 & 割边(桥)

割点和点双是针对无向图来说的,所以我们重新定义 ,为不经过父节点能追溯到的最
小的 。

根据low 和 dfn的定义:得出判断割点(非根节点)的判断条件:low\(v\)>=dfn\(u\)
意思是:不走父节点,无法追溯到父节点以前的节点,那u一定是割点。

换句话说,u节点把v“卡住了”。

以上是针对非根节点来说的,如果是根节点,只需要判断它的子树是否大于 棵即可,原因是如果断开根节点,子树必定不连通。

void tarjan(int x,int fa,int root){
	dfn[x]=low[x]=++tot;
	int ch=0;
	for(int i=head[x];i;i=a[i].next ){
		int now=a[i].v ;
		if(!dfn[now]){
			ch++;
			tarjan(now,x,root);
			low[x]=min(low[x],low[now]);
			if(low[now]>=dfn[x]&&x!=root) f[x]=1;
		}
		else if(now!=fa) low[x]=min(dfn[now],low[x]);//不是根节点 (判断割点(非根节点)的判断条件:low[v]>=dfn[u]) 
	}
	if(x==root&&ch>=2) f[x]=1;//是根节点的情况 子树大于1  
}

割边和点双的区别为\(low[v] \geq dfn[u]\) --> \(low[v]> dfn[u]\)

就是不经过父节点没有别的路可以回去,那\(u\)\(fa\)之间一定是割边。

void tarjan(int x,int fa){
	low[x]=dfn[x]=++tot;
	for(int i=head[x];i;i=a[i].next ){
		int v=a[i].v ;
		if(!dfn[v]){
        tarjan(v,x),low[x]=min(low[x],low[v]);
        if(dfn[u]<low[v]) G++;
        }
		else if(vis^fa)
			low[x]=min(low[x],low[now]);
	}
}

点/边双连通分量

在一张连通的无向图中,对于两个点 \(u\)\(v\),如果无论删去哪条(只能删去一条)都不能使它们不连通,我们就说 \(u\)\(v\) 边双连通

在一张连通的无向图中,对于两个点 \(u\)\(v\),如果无论删去哪个(只能删去一个,且不能删 \(u\)\(v\) 自己)都不能使它们不连通,我们就说 \(u\)\(v\) 点双连通

边双连通具有传递性,即,若 \(x\) , \(y\) 边双连通,\(y\) , \(z\) 边双连通,则 \(x\) , \(z\) 边双连通。

点双与割点的区别是不需要判\(root\),需要同样满足\(low[v] \ge dfn[u]\)

求点双分量,可以在求割点的时候顺便求出来。

当我们发现 \(low[v]\ge dfn[u]\) 时,就说明 \(v\) 及其子树(加上 \(u\))一起形成了一个点双。

此时,我们将 栈内的点一直pop,pop 到 \(v\) 为止(包括 \(v\)),但是 不包括 \(u\)。这是因为 \(u\) 作为割点,可能还属于别的点双分量,之后还要用到。

• 求点双时,我们并不关心 \(u\) 本身是否为割点,只要出现了 \(low[v]\ge dfn[u]\) ,就说明出现了一个新点双。(例如,在只有 \(1,2\) 这两个点的情况下,不存在割点,但是 \(1,2\) 仍然是一个点双)。

cofe:

bool cut[maxn];
vector<int> dcc_list[maxn];
void dfs(int u, int p) {
    dfn[u] = low[u] = ++id;
    st[++tail] = u;
    int child = 0;

    for (int e = head[u]; e; e = edges[e].nxt) {
        int v = edges[e].to;
        if (v == p) continue;
        if (dfn[v]) {
            low[u] = min(low[u], dfn[v]);
        } else {
            child++;
            dfs(v, u);
            low[u] = min(low[u], low[v]);
            if (low[v] >= dfn[u]) { 
                from[v] = ++dcc;
                while (st[tail] != v) {
                    int cur = st[tail--];
                    from[cur] = from[v];
                    dcc_list[dcc].push_back(cur);
                }
                tail--;
                dcc_list[dcc].push_back(v);
                dcc_list[dcc].push_back(u); 
            }
            if (low[v] >= dfn[u] && p) cut[u] = 1;  // 割点(非根节点)
        }
    }
    if (!p && child >= 2) cut[u] = 1;  // 割点(根节点)
}

割边/边双连通分量

const int N=5e5+10;

int n,m,x,y,cnt=-1,w[N],head[N],xo[N];
int tot,dfn[N],low[N],qiao[N<<1],TOT,Dfn[N];
struct node{
	int u,v,nxt;
}a[N<<1];

void add(int u,int v){
	a[++cnt]=(node){u,v,head[u]};
	head[u]=cnt;
}

il int Max(int x, int y) {return x > y ? x: y;}
il int Min(int x, int y) {return x < y ? x: y;}

il int read(){
	int f = 1, x = 0; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-')f = -f;ch = getchar();}
	while(ch >= '0' && ch <= '9'){x = x*10 + ch-'0';ch = getchar();}
	return f * x;
}

void tarjan(int x,int fa){
	dfn[x]=low[x]=++tot;
	for(int i=head[x];i!=-1;i=a[i].nxt){
		int v=a[i].v;
		if(!dfn[v]){//再往下找的话才可能low[v]>low[x],既然已经走过了,那low[v]必然小于low[x]
			tarjan(v,x);
			low[x]=min(low[x],low[v]);
			if(low[v]>low[x]){//正反两个边都标记为桥 
				qiao[i]=1;
				qiao[i^1]=1;
			}
		}
		else if(v^fa) low[x]=min(low[x],low[v]);
	}
}

void dfs(int x){
	Dfn[x]=TOT;
	xo[TOT]^=w[x];
	for(int i=head[x];i!=-1;i=a[i].nxt){
		int v=a[i].v;
		if(qiao[i]) continue;
		if(!Dfn[v])dfs(v);
	}
}

signed main(){
	n=read();m=read();
	cl(head,-1);
	rep(i,1,n) w[i]=read();
	rep(i,1,m){
		x=read();y=read();
		add(x,y);
		add(y,x);
	}
	rep(i,1,n) if(!dfn[i]) tarjan(i,0);
	rep(i,1,n) if(!Dfn[i]) ++TOT,dfs(i);
	sort(xo+1,xo+1+TOT);
	rep(i,1,TOT) cout<<xo[i]<<'\n';
	return 0;
}

边双连通分量

const int N=5e5+10;

int n,m,x,y,cnt=-1,head[N];
int tot,TOT,Cnt,dfn[N],low[N],Dfn[N],qiao[N<<1];

struct node{
	int u,v,nxt;
}a[N<<1];

void add(int u,int v){
	a[++cnt]=(node){u,v,head[u]};
	head[u]=cnt;
}

il int Max(int x, int y) {return x > y ? x: y;}
il int Min(int x, int y) {return x < y ? x: y;}

il int read(){
	int f = 1, x = 0; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-')f = -f;ch = getchar();}
	while(ch >= '0' && ch <= '9'){x = x*10 + ch-'0';ch = getchar();}
	return f * x;
}

void tarjan(int x,int fa){
	dfn[x]=low[x]=++tot;
	for(int i=head[x];i!=-1;i=a[i].nxt){
		int v=a[i].v;
		if(!dfn[v]){
			tarjan(v,x);
			low[x]=min(low[x],low[v]);
			if(low[v]>low[x]){
				qiao[i]=1;
				qiao[i^1]=1;
			}
		}
		else if(v^fa) low[x]=min(low[x],low[v]);
	}
}

void dfs(int x){
	Dfn[x]=TOT;
	for(int i=head[x];i!=-1;i=a[i].nxt){
		if(qiao[i]) continue;
		if(!Dfn[a[i].v]) dfs(a[i].v);
	}
}

signed main(){
	n=read();m=read();
	cl(head,-1);
	rep(i,1,m){
		x=read();y=read();
		add(x,y);
		add(y,x);
	}
	rep(i,1,n) if(!dfn[i]) tarjan(i,0);
	rep(i,1,n) if(!Dfn[i]) ++TOT,dfs(i);
	cout<<TOT;
	return 0;
}

点双连通分量

const int N=2e6+10;

int n,m,x,y,cnt,head[N];
int tot,sc,Dcc,low[N],dfn[N],sta[N];
vector<int>dcc[N];

struct node{
	int u,v,nxt;
}a[N<<1];

void add(int u,int v){
	a[++cnt]=(node){u,v,head[u]};
	head[u]=cnt;
}

il int read(){
	int f = 1, x = 0; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-')f = -f;ch = getchar();}
	while(ch >= '0' && ch <= '9'){x = x*10 + ch-'0';ch = getchar();}
	return f * x;
}

void tarjan(int x,int fa){
	dfn[x]=low[x]=++tot;sta[++sc]=x;
	int ch=0;
	for(int i=head[x];i;i=a[i].nxt){
		int v=a[i].v;
		if(!dfn[v]) {
			++ch;
			tarjan(v,x);
			low[x]=min(low[x],low[v]);
			if(low[v]>=dfn[x]){
				++Dcc;
				int Top=sta[sc--];
				while(Top!=v) dcc[Dcc].push_back(Top),Top=sta[sc--];
				dcc[Dcc].push_back(v);
				dcc[Dcc].push_back(x);
			}
		}
		else if(v^fa) low[x]=min(low[x],low[v]);
	}
	if(!fa&&!ch) dcc[++Dcc].push_back(x);
}

signed main(){
	n=read();m=read();
	rep(i,1,m){
		x=read();y=read();
		add(x,y);
		add(y,x);
	}
	rep(i,1,n) if(!dfn[i]) tarjan(i,0);
	cout<<Dcc<<'\n';
	rep(i,1,Dcc){
		int l=dcc[i].size();
		cout<<l<<" ";
		rep(j,0,l-1)cout<<dcc[i][j]<<' ';
		cout<<'\n';
	}
	return 0;
}

点双连通分量

const int N=1e5+10;

int n,m,x,y,Dcc,cnt,head[N];
int sc,tot,low[N],dfn[N],sta[N];
vector<int>dcc[N];
struct node{
	int u,v,nxt;
}a[N<<1];

void add(int u,int v){
	a[++cnt]=(node){u,v,head[u]};
	head[u]=cnt;
}

void tarjan(int x,int fa){
	low[x]=dfn[x]=++tot;sta[++sc]=x;
	int ch=0;
	for(int i=head[x];i;i=a[i].nxt){
		int v=a[i].v;
		if(!dfn[v]){
			++ch;
			tarjan(v,x);
			low[x]=min(low[x],low[v]);
			if(low[v]>=dfn[x]){
				++Dcc;
				int Top=sta[sc--];
				while(Top!=v){
					dcc[Dcc].push_back(Top);
					Top=sta[sc--];
				}
				dcc[Dcc].push_back(v);
				dcc[Dcc].push_back(x);
			}
		}
		else if(v^fa) low[x]=min(low[x],low[v]);
	}
	if(!fa&&!ch) dcc[++Dcc].push_back(x);
}

signed main(){
	n=read();m=read();
	rep(i,1,m){
		x=read();y=read();
		add(x,y);
		add(y,x);
	}
	rep(i,1,n) if(!dfn[i]) tarjan(i,0);
	rep(i,1,Dcc){
		int l=dcc[i].size();
		rep(j,0,l-1) cout<<dcc[i][j]<<" ";
		cout<<'\n';
	}
	return 0;
}

矿场搭建

最近脑袋有些浑浊,所以这当然不完全是我自己写的。

const int N=2e6+10;
int t,n,x,y,a1,a2,T,cnt,head[N];
int sc,tot,ctot,Mt,Dcc,dfn[N],low[N],sta[N],cut[N];//cut record gd
vector<int>dcc[N];

struct node{
	int u,v,nxt;
}a[N];

void add(int u,int v){
	a[++cnt]=(node){u,v,head[u]};
	head[u]=cnt;
}

void clean(){
	cl(dfn,0);
	cl(low,0);
	cl(dcc,0);
	cl(cut,0);
	cl(head,0);
//	sc=0;
	sc=-1;
	tot=0;Dcc=0;Mt=0;
	cnt=0;Dcc=0;a1=0;a2=1;
}

void tarjan(int x,int fa){
	dfn[x]=low[x]=++tot;sta[++sc]=x;
	int ch=0;
	for(int i=head[x];i;i=a[i].nxt){
		int v=a[i].v;
		if(!dfn[v]){
			++ch;
			tarjan(v,x);
			low[x]=min(low[x],low[v]);
			if(low[v]>=dfn[x]) {//Wrong
				++Dcc;
				int Top=sta[sc--];
				while(Top!=v){
					dcc[Dcc].pb(Top);
					Top=sta[sc--];
				}
				dcc[Dcc].pb(v);
				dcc[Dcc].pb(x);
			}
			if(low[v]>=dfn[x]&&fa) cut[x]=1;
		}
		else if(v^fa) low[x]=min(low[x],dfn[v]);
	}
	if(!fa&&ch>=2) cut[x]=1;
}

signed main(){
	n=read();
	while(n){
		++T;
		clean();
		rep(i,1,n){
			x=read();y=read();
			Mt=Max(Max(x,y),Mt);
			add(x,y);
			add(y,x);
		}
		rep(i,1,Mt) if(!dfn[i]) tarjan(i,0);
		rep(i,1,Dcc){
			int cut_c=0;
			int sz=dcc[i].size();
			rep(j,0,dcc[i].size()-1){
				if(cut[dcc[i][j]]) ++cut_c;
			}
			if(cut_c>=2)continue;
			if(cut_c==1) ++a1,a2*=(ll)(sz-1);
			if(cut_c==0) a1+=2,a2*=(ll)(sz)*(ll)(sz-1)/2; 
		}
		cout<<"Case "<<T<<": "<<Max(a1,2)<<" "<<a2<<'\n'; 
		n=read();
	}
	return 0;
}
posted @ 2023-06-19 19:36  XYini  阅读(1)  评论(0)    收藏  举报