【图论】连通性问题

本文介绍了如何使用倍增法求解LCA问题,以及涉及图论中的强连通分量的tarjan算法和割点的检测。还提及了与PopularCows相关的问题和可达性的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

lca

1.1倍增法求lca

【manim | 算法】7分钟学会倍增法求解LCA_哔哩哔哩_bilibili

递归初始化

void dfs(long u,long father){
dep[u]=dep[father]+1;//只在这里初始化dep

for(long i=1;(1<<i)<=dep[u];i++)
    fa[u][i]=fa[fa[u][i-1]][i-1];//只这里用的倍增

for(long i=head[u];~i;i=edge[i].next){
    long v=edge[i].to;
    if(v==father)continue;
    fa[v][0]=u;
    dfs(v,u);
}

}



lca主体 



long lca(long x,long y){
if(dep[x]<dep[y])swap(x,y);

for(int i=20;i>=0;i--){//跳到同一个深度
    if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
    if(x==y)return x;
}

for(int i=20;i>=0;i--){
    if(fa[x][i]!=fa[y][i]){//一起跳
        x=fa[x][i];
        y=fa[y][i];
    }
}
return fa[x][0];
}

开始交了几发都不对,以为必须用tarjan等快方法

今天看到输出少了一个,n-1条边,s的父是0,ac了

#include<iostream>
using namespace std;

long n,q,s;
struct EDGE{
long  next;
long to;

}edge[1000001];
long head[500001];
long tot=0;

void addedge(long u,long v){
edge[++tot].next=head[u];
edge[tot].to=v;

head[u]=tot;
}
long dep[500001];
long fa[500001][21];//最多跳20次
void dfs(long u,long father){
dep[u]=dep[father]+1;

for(long i=1;(1<<i)<=dep[u];i++)
    fa[u][i]=fa[fa[u][i-1]][i-1];

for(long i=head[u];~i;i=edge[i].next){
    long v=edge[i].to;
    if(v==father)continue;
    fa[v][0]=u;
    dfs(v,u);
}

}

long lca(long x,long y){
if(dep[x]<dep[y])swap(x,y);

for(int i=20;i>=0;i--){
    if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
    if(x==y)return x;
}

for(int i=20;i>=0;i--){
    if(fa[x][i]!=fa[y][i]){
        x=fa[x][i];
        y=fa[y][i];
    }
}
return fa[x][0];
}


int main(){

cin>>n>>q>>s;
for(long i=1;i<=n;i++)head[i]=-1;

for(long i=1;i<=n-1;i++){
    long u,v;cin>>u>>v;
    addedge(u,v);addedge(v,u);
}
dfs(s,0);
while(q--){
    long a,b;cin>>a>>b;
    if(a==b)cout<<a<<endl;
    else
    cout<<lca(a,b)<<endl;
}
}

1.2Design the city

Design the city - ZOJ 3195 - Virtual Judge (vjudge.net.cn)

先mark着

#include<iostream>
using namespace std;
//连接三个点最短路径
//无向无环
//必然有一个深度最小

//就当图来做,这三个点的最小生成树

//两两找最近公共祖先

-------------------------------

2.1强连通分量

B3609 [图论与代数结构 701] 强连通分量 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include<iostream>
#include<vector>
#include<stack>
using namespace std;

stack<int> s;
int low[10001];int dfn[10001];
bool vis[10001];
int scc[10001];
int block_cnt;
int scc_cnt;
vector<int> scc_zong[10001];
vector<int> G[10001];
int n,m;

void tarjan(int x){
low[x]=dfn[x]=++block_cnt;
s.push(x);
vis[x]=true;

for(int i=0;i<G[x].size();i++){

    int v=G[x][i];
    if(!vis[v]){
            //为什么是low[v]
            //如果v是没走过,当场走的话,可以追溯v更新后的
            //如果v走过,但不属于联通分量,v就没有追溯的,现在收入
            //觉得一样,一回试试low[v]
        tarjan(v);
        low[x]=min(low[x],low[v]);
    }
    else if(!scc[v]){
        low[x]=min(low[x],dfn[v]);
    }
}

//这样走过一通后,如果是最栈底的点
if(dfn[x]==low[x]){
        scc_cnt++;
while(1){
int temp=s.top();
    s.pop();
    scc[temp]=scc_cnt;
    scc_zong[scc_cnt].push_back(temp);
    if(temp==x)break;
}

}
}

void solu(){

for(int i=0;i<=n;i++)vis[i]=0;

cout<<scc_cnt-1;
for(int i=1;i<=n;i++){
        if(!vis[i]){
                cout<<endl;
                vis[i]=1;
                cout<<i;
                    for(int j=i+1;j<=n;j++){
                    if(scc[i]==scc[j]){
                        vis[j]=1;
                    cout<<" "<<j;
                    }
            }


        }

}


//想欧拉筛
/*for(int i=1;i<=n;i++){
        if(!vis[i]){
            int gai_scc=scc[i];

            for(int j=0;i<gai_scc[])
        }

}*/


/*for(int i=scc_cnt;i>=1;i--){
    cout<<endl;
    for(int j=scc_zong[i].size()-1;j>=0;j--){
            cout<<scc_zong[i][j];
        if(j!=0)cout<<" ";
    }

}
*/

/*for(int i=1;i<=scc_cnt;i++){if(i!=1)cout<<endl;
    for(int j=0;j<scc_zong[i].size();j++){
        if(j!=0)cout<<" ";
        cout<<scc_zong[i][j];
    }*/



}


int main(){

cin>>n>>m;
for(int i=1;i<=n;i++)G[0].push_back(i);
//这样应该就能按顺序遍历1,2,3,点所在的强连通分量了
//啊啊错了
while(m--){
    int u,v;
    cin>>u>>v;
    G[u].push_back(v);
}

tarjan(0);//0不会只追溯到自己的
solu();


}

这里是加了一个万能源点0,从后面模板割点学到了tarjan整图不连通的另一个解决方案

for(int i=1;i<=n;i++)
if(!dfn[x])tarjan(x);

2.2牛的舞会

P2863 [USACO06JAN] The Cow Prom S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

2.3popular cows

2186 -- Popular Cows (poj.org)

P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

2.4可达性

可达性 (nowcoder.com)

每个入度为0的联通块取一个最小点


#include<iostream>
#include<vector>
#include<stack>
using namespace std;

bool ans[100001];
stack<long> s;
long low[100001];long dfn[100001];
bool vis[100001];
long color[100001];
long block_cnt;
long scc_cnt;
vector<long> scc[100001];
vector<long> G[100001];
long n,m;

void tarjan(long x){
low[x]=dfn[x]=++block_cnt;
s.push(x);
vis[x]=true;

for(long i=0;i<G[x].size();i++){

    long v=G[x][i];
    if(!vis[v]){

        tarjan(v);
        low[x]=min(low[x],low[v]);
    }
    else if(!color[v]){
        low[x]=min(low[x],dfn[v]);
    }
}

//这样走过一通后,如果是最栈底的点
if(dfn[x]==low[x]){
        scc_cnt++;
while(1){
long temp=s.top();
    s.pop();
    color[temp]=scc_cnt;
    scc[scc_cnt].push_back(temp);
    if(temp==x)break;
}

}
}

//有那种能到任意点的,说明暗暗给出图是弱联通
void solve(){
//如果u和v不是一个联通酷爱
//v记录入度

//入度为0的快加上

for(long i=1;i<=scc_cnt;i++){
    ans[i]=true;
}

for(long i=1;i<=n;i++){
    for(long j=0;j<G[i].size();j++){
        long v=G[i][j];
        if(color[i]!=color[v]){
            ans[color[v]]=0;

                  }
    }
}
long out=0;

for(long i=1;i<=scc_cnt;i++){//i是联通块编号
    if(ans[i]){out++;}}

    cout<<out<<endl;
//入度为0的块,每个取一个最小的点

bool flag=1;
for(long i=1;i<=scc_cnt;i++){//i是联通块编号
    if(ans[i]){
            long temp=n;
        for(long j=0;j<scc[i].size();j++){
            temp=min(temp,scc[i][j]);
        }
        if(flag==1)flag=0;
        else cout<<" ";
    cout<<temp;
    }
}
}

int main(){

cin>>n>>m;

while(m--){
    long u,v;
    cin>>u>>v;
    G[u].push_back(v);
}
for(long i=1;i<=n;i++){


if(!dfn[i]){tarjan(i);}
}


solve();


}

------------------------------- 

 模板缩点

 P3387 【模板】缩点 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

stl不知道怎么用,先mark

更新:stl会用了,dp不会了

#include<iostream>
#include<vector>
#include<stack>
using namespace std;

bool vis[10001];
int dfn[10001];
int low[10001];
int block_cnt;
stack<int> s;
vector<int> G[10001];

void tarjan(int x){
dfn[x]=low[x]=++block_cnt;
s.push(x);
vis[x]=1;


for(int i=head[x];~i;i=edge[i].next){
    int v=edge[i].to;
    if(!vis[v]){
        tarjan(v);
        low[x]=min(low[x],low[v]);

    }
    else if(!scc[v]){
        low[x]=min(low[x],dfn[v]);
    }
}
if(low[x]==dfn[x]){
    scc_cnt++;
    while(1){
        int temp=s.top();
        s.pop();
        scc[temp]=scc_cnt;
        quanscc[scc_cnt]+=quanpoint[temp];
        if(temp==x)break;
    }
}

}


void suo(){
for(int i=1;i<=n;i++){
    for(int j=0;j<G[u].size();j++){
        if(scc[i]!=scc[G[i][j]]){
            addedge(scc[i],scc[G[i][j]]);
        }
    }
}

//dp
//状压
}


int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>quanpoint[i];
while(m--){
    cin>>u>>v;
    G[u].push_back(v);
}


}

-------------------------------

模板割点

P3388 【模板】割点(割顶) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include<iostream>
#include<vector>
#include<stack>
using namespace std;


int n;long m;
stack<int> s;
int block_cnt;
int dfn[20001];
int low[20001];
int root;
bool ge[20001];
vector<int> G[20001];
//割点ge[x]=0
//非root,有儿子,low[v]>=dfn[x]
//root,没有连线

//传参root,就对了

void tarjan(int x,int root){
dfn[x]=low[x]=++block_cnt;
s.push(x);
int ch=0;

for(int i=0;i<G[x].size();i++){
    int v=G[x][i];

    if(!dfn[v]){
        ch++;//如果遍历过,说明是两个子树中间有边的情况
        //不是一个v一条边
        tarjan(v,root);//ch在tarjan(x)内定义,不会到v内
        low[x]=min(low[x],low[v]);
        if(x!=root&&low[v]>=dfn[x]){ge[x]=1;}
//无论如何,认为所有v的low都必须》=dfn[x],
            //只一个就决定了怎么能行

    }
    else {
        low[x]=min(low[x],dfn[v]);
    }

}

if(ch>=2&&root==x){
    ge[x]=1;
}

}

int main(){
cin>>n>>m;

while(m--){
    int u,v;
    cin>>u>>v;
    G[u].push_back(v);
    G[v].push_back(u);
}
for(int i=1;i<=n;i++){
    if(!dfn[i]){

            //root=i;
    //root是一个滚动的根
        tarjan(i,i);
    }
}

int ans=0;
for(int i=1;i<=n;i++){
    if(ge[i])ans++;
}
cout<<ans<<endl;

bool flag=1;
for(int i=1;i<=n;i++)

if(ge[i]){
    if(flag==1){flag=0;}
    else {cout<<" ";}
        cout<<i;}

}

### 图论连通性的定义 在图论中,连通性是指一个图中任意两个顶点之间是否存在一条路径使得它们能够相互到达。如果在一个无向图中,任何一对顶点都存在至少一条路径相连,则该图为连通图;否则称为非连通图[^1]。 对于有向图而言,强连通的概念更为重要。当有向图中的每对顶点 \(u\) 和 \(v\) 都满足可以从 \(u\) 到达 \(v\) 并且也可以从 \(v\) 返回到 \(u\) 时,这样的子图被称为强连通分量。 ### 连通性检测的算法及其实现 #### Kosaraju 算法 Kosaraju 算法是一种用于寻找有向图中所有强连通分量的有效方法。它利用两次深度优先搜索(DFS),第一次是在原始图上执行 DFS 得到逆序排列的顶点序列,第二次则是在反转后的图上按照上述顺序再次进行 DFS 来划分不同的强连通分量。 以下是简单的 Python 实现: ```python def kosaraju(graph): visited = set() stack = [] def first_dfs(node): if node not in visited: visited.add(node) for neighbor in graph[node]: first_dfs(neighbor) stack.append(node) reversed_graph = {} for key, values in graph.items(): if key not in reversed_graph: reversed_graph[key] = [] for value in values: if value not in reversed_graph: reversed_graph[value] = [] reversed_graph[value].append(key) sccs = [] def second_dfs(node, component): if node not in visited: visited.add(node) component.append(node) for neighbor in reversed_graph.get(node, []): if neighbor not in visited: second_dfs(neighbor, component) for vertex in list(graph.keys()): if vertex not in visited: first_dfs(vertex) visited.clear() while stack: v = stack.pop() if v not in visited: current_scc = [] second_dfs(v, current_scc) if current_scc: sccs.append(current_scc) return sccs ``` #### Union-Find 算法 Union-Find 是一种解决动态连通性问题的数据结构技术,广泛应用于最小生成树等问题之中。它的核心在于维护一组元素之间的等价关系,并支持两种主要的操作:“查找”某元素所属集合以及“合并”两个不同集合][^[^24]。 下面是一个基于路径压缩优化版本的 Quick-Union 实现示例: ```python class UnionFind: def __init__(self, n): self.id = list(range(n)) self.size = [1]*n def root(self, i): while i != self.id[i]: self.id[i] = self.id[self.id[i]] # Path compression i = self.id[i] return i def connected(self, p, q): return self.root(p) == self.root(q) def union(self, p, q): rp = self.root(p) rq = self.root(q) if rp == rq: return if self.size[rp] < self.size[rq]: self.id[rp] = rq self.size[rq] += self.size[rp] else: self.id[rq] = rp self.size[rp] += self.size[rq] ``` #### 基于 DFS 的连通分量检测 这种方法适用于无向图的情况,通过递归访问每一个未被标记过的节点来探索整个连通区域[^3]。 Python 示例代码如下所示: ```python def dfs_connected_components(graph): visited = set() components = [] def explore(node, comp): visited.add(node) comp.append(node) for neighbor in graph[node]: if neighbor not in visited: explore(neighbor, comp) for vertex in graph: if vertex not in visited: component = [] explore(vertex, component) components.append(component) return components ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值