【图论】连通性问题

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

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;}

}

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值