Living-Dream 系列笔记 第86期

边双连通分量

概念:若在无向图 \(G\) 中,存在一个极大子图 \(G'\),使得 \(G'\) 中没有割边,则称 \(G'\)\(G\) 的一个边双连通分量,记作 \(\texttt{E-DCC}\)

使用场景:将无向图转化为一棵树(即无向图上的缩点)。

求解步骤:确定割边,再遍历所有点且不经过割边,那么能联通的点都是即在同一个 \(\texttt{E-DCC}\) 中。

T103489

板子。

code
#include<bits/stdc++.h>
using namespace std;

const int N=3e5+5;
int n,m,cnt,id;
int dfn[N],low[N],dcc[N];
bool bridge[N];
struct NODE{
	int v,i;
};
vector<NODE> G[N];

void tarjan(int cur,int edg){
	dfn[cur]=low[cur]=++cnt;
	for(auto nxt:G[cur]){
		if(!dfn[nxt.v]){
			tarjan(nxt.v,nxt.i);
			low[cur]=min(low[cur],low[nxt.v]);
			if(low[nxt.v]>dfn[cur])
				bridge[nxt.i]=1;
		}
		else if(nxt.i!=edg){
			low[cur]=min(low[cur],dfn[nxt.v]);
		}
	}
}
void dfs(int cur){
	dcc[cur]=id;
	for(auto nxt:G[cur]){
		if(dcc[nxt.v]||bridge[nxt.i])
			continue;
		dfs(nxt.v);
	}
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		G[u].push_back({v,i});
		G[v].push_back({u,i}); 
	}
	for(int i=1;i<=n;i++)
		if(!dfn[i])
			tarjan(i,0);
	for(int i=1;i<=n;i++)
		if(!dcc[i])
			++id,dfs(i);
	cout<<id; 
	return 0;
}

POJ3352

结论题。

考虑缩点,形成的树上就都是割边了。

现在要连边成环从而消去割边带来的影响。

要求连边最少,也就是每次消去的割边要尽可能多。

怎么消去的最多?这等价于问树上哪条路径最长。

那显然就是连直径的两个端点。

而众所周知,直径的端点只有可能是度数为 \(1\) 的节点。

即叶子节点 + 度数为 \(1\) 的根(根在度数 \(>1\) 时显然不会比叶子节点更优)。

于是寻找度数为 \(1\) 的根两两相连即可,有落单的则还需连一次。

令度数为 \(1\) 的点有 \(x\) 个,这说明答案即为 \(\lceil \frac{x}{2} \rceil\)

code
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
using namespace std;

const int N=1e3+5;
int n,m,cnt,id;
int dfn[N],low[N],dcc[N],dg[N];
bool bridge[N],ok[N][N];
struct NODE{
	int v,i;
};
vector<NODE> G[N];
struct EDGE{
	int u,v;
}e[N];

void tarjan(int cur,int edg){
	dfn[cur]=low[cur]=++cnt;
	for(int nxt=0;nxt<G[cur].size();nxt++){
		if(!dfn[G[cur][nxt].v]){
			tarjan(G[cur][nxt].v,G[cur][nxt].i);
			low[cur]=min(low[cur],low[G[cur][nxt].v]);
			if(low[G[cur][nxt].v]>dfn[cur])
				bridge[G[cur][nxt].i]=1;
		}
		else if(G[cur][nxt].i!=edg){
			low[cur]=min(low[cur],dfn[G[cur][nxt].v]);
		}
	}
}
void dfs(int cur){
	dcc[cur]=id;
	for(int nxt=0;nxt<G[cur].size();nxt++){
		if(dcc[G[cur][nxt].v]||bridge[G[cur][nxt].i])
			continue;
		dfs(G[cur][nxt].v);
	}
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		e[i]={u,v};
		G[u].push_back({v,i});
		G[v].push_back({u,i}); 
	}
	for(int i=1;i<=n;i++)
		if(!dfn[i])
			tarjan(i,0);
	for(int i=1;i<=n;i++)
		if(!dcc[i])
			++id,dfs(i);
	for(int i=1;i<=m;i++){
		if(bridge[i]){
			dg[dcc[e[i].u]]++;
			dg[dcc[e[i].v]]++;
		}
	}
	int ans=0;
	for(int i=1;i<=id;i++)
		if(dg[i]==1)
			ans++;
	cout<<(ans+1)/2;
	return 0;
}

总结:做题时注意题面信息的转化。

POJ3694

如果只有一次询问,那么答案就是两个点所在的 \(\texttt{E-DCC}\) 在缩点后的树上的简单路径长度。

考虑多组询问,其实没什么区别,就是可能有重复的边,

于是我们不能直接求了,只能一步步往 LCA 跳,

但是这个时候再去走这些重复边就是浪费,于是我们用并查集优化一下即可。

思维难度很低对吧,但是我这个唐诗儿调了一上午 /fn/fn/fn

code
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
using namespace std;

const int N=1e5+5,M=2e5+5;
int n,m,q;
int cnt,id,ans,tot,test;
int dfn[N],low[N],dcc[N];
int dp[N][18],dep[N],fa[N];
bool bridge[M<<1];
struct NODE{
	int v,i;
};
vector<NODE> G[N];
struct EDGE{
	int u,v;
}e[M];
vector<int> T[N];

inline int read(){
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9')
        x=x*10+ch-'0',ch=getchar();
    return x*f;
}
void write(int x){
    if(x<0)
        putchar('-'),x=-x;
    if(x>9)
        write(x/10);
    putchar(x%10+'0');
    return;
}
void tarjan(int cur,int edg){
	dfn[cur]=low[cur]=++cnt;
	for(int nxt=0;nxt<G[cur].size();nxt++){
		if(!dfn[G[cur][nxt].v]){
			tarjan(G[cur][nxt].v,G[cur][nxt].i);
			low[cur]=min(low[cur],low[G[cur][nxt].v]);
			if(low[G[cur][nxt].v]>dfn[cur])
				bridge[G[cur][nxt].i]=1;
		}
		else if(G[cur][nxt].i!=edg){
			low[cur]=min(low[cur],dfn[G[cur][nxt].v]);
		}
	}
}
void dfs(int cur){
	dcc[cur]=id;
	for(int nxt=0;nxt<G[cur].size();nxt++){
		if(dcc[G[cur][nxt].v]||bridge[G[cur][nxt].i])
			continue;
		dfs(G[cur][nxt].v);
	}
}
void init(){
	memset(dfn,0,sizeof dfn);
	memset(low,0,sizeof low);
	memset(dcc,0,sizeof dcc);
	memset(bridge,0,sizeof bridge);
	cnt=id=0;
	for(int i=1;i<=n;i++)
		G[i].clear();
}
void Init(){
	for(int i=1;i<=id;i++)
		T[i].clear(),fa[i]=i;
}
void init_lca(int cur,int father){
	dep[cur]=dep[father]+1;
	dp[cur][0]=father;
	for(int i=1;(1<<i)<=dep[cur];i++)
		dp[cur][i]=dp[dp[cur][i-1]][i-1];
	for(int nxt=0;nxt<T[cur].size();nxt++){
		if(T[cur][nxt]==father)
			continue;
		init_lca(T[cur][nxt],cur);
	}
}
int get_lca(int x,int y){
	if(dep[x]<dep[y])
		swap(x,y);
	for(int i=17;i>=0;i--)
		if(dep[dp[x][i]]>=dep[y])
			x=dp[x][i];
	if(x==y)
		return x;
	for(int i=17;i>=0;i--)
		if(dp[x][i]!=dp[y][i])
			x=dp[x][i],y=dp[y][i];
	return dp[x][0];
}
int fnd(int x){
	return fa[x]==x?x:fa[x]=fnd(fa[x]);
}
void jump(int x,int ed){
	x=fnd(x);
	while(dep[x]>dep[ed]){
		fa[x]=dp[x][0];
		ans--;
		x=fnd(x);
	}
} 

int main(){
	while(1){
		n=read(),m=read();
		if(n==0&&m==0)
			break;
		cout<<"Case "<<++test<<":\n";
		init();
		for(int i=1,u,v;i<=m;i++){
			u=read(),v=read();
			e[i]={u,v};
			G[u].push_back({v,i});
			G[v].push_back({u,i});
		}
		for(int i=1;i<=n;i++)
			if(!dfn[i])
				tarjan(i,0);
		for(int i=1;i<=n;i++)
			if(!dcc[i])
				++id,dfs(i);
		Init();
		for(int i=1;i<=m;i++){
			if(bridge[i]){
				T[dcc[e[i].u]].push_back(dcc[e[i].v]);
				T[dcc[e[i].v]].push_back(dcc[e[i].u]);
			}
		}
		q=read();
		init_lca(1,0);
		ans=id-1;
		while(q--){
			int a,b;
			cin>>a>>b;
			a=dcc[a],b=dcc[b];
			int lca=get_lca(a,b);
			jump(a,lca),jump(b,lca);
			write(ans);
			putchar('\n');
		}
		putchar('\n');
	}
	return 0;
}

总结:做题从简单情况入手。

CF231E

简单题,10 min 一遍过。tj

posted @ 2024-11-16 20:25  _XOFqwq  阅读(11)  评论(0编辑  收藏  举报