边双连通分量
概念:若在无向图 \(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。
标签:Living,cur,nxt,int,86,dfn,low,include,Dream From: https://www.cnblogs.com/XOF-0-0/p/18549765