算法:强连通分量(SCC) Tarjan算法

强连通分量,不能再加任何一个点了,再加一个点就不是强连通了

 

vector<int>e[N];
int dfn[N],low[N],tot;
bool instk[N];
int scc[N],siz[N],cnt;
void tarjan(int x){
    //入x时,盖戳,入栈
    dfn[x]=low[x]=++tot;
    q.push(x);
    instk[x]=true;
    for(auto y:e[x]){
        if(!dfn[y]){//若y尚未访问
            tarjan(y);
            low[x]=min(low[x],low[y]);//回x时更新low
        }
        else if(instk[y]){//若y已被访问且在栈中
            low[x]=min(low[x],dfn[y]);//更新low
        }
    }
    //离x时,记录SCC
    if(dfn[x]==low[x]){//若x是SCC的根
        int y;
        cnt++;
        do{
            y=q.top();
            q.pop();
            instk[y]=false;
            scc[y]=cnt;//SCC编号
            siz[cnt]++;//SCC大小
        }while(y!=x);
    }
}

[USACO06JAN] The Cow Prom S - 洛谷

AC代码:

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e4+10;
int dfn[N],low[N],tot;
bool instk[N];
int siz[N],cnt;
stack<int>q;
vector<vector<int>>e(N);
int n,m;
void tarjan(int x){
    dfn[x]=low[x]=++tot;
    q.push(x);
    instk[x]=true;
    for(auto v:e[x]){
        if(!dfn[v]){
            tarjan(v);
            low[x]=min(low[x],low[v]);
        }
        else if(instk[v]){
            low[x]=min(low[x],dfn[v]);
        }
    }
    if(dfn[x]==low[x]){
        int y;
        cnt++;
        do{
            y=q.top();
            q.pop();
            instk[y]=false;
            siz[cnt]++;
        }while(y!=x);
    }
}
void solve() {
    cin>>n>>m;
    while(m--){
        int a,b;
        cin>>a>>b;
        e[a].push_back(b);
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i]) tarjan(i);
    }
    int ans=0;
    for(int i=1;i<=cnt;i++){
        if(siz[i]>1) ans++;
    }
    cout<<ans<<endl;
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
//    cin>>t;
    while(t--) {
        solve();
    }
    return 0;
}

 Trajan SCC缩点

 

我们加边的时候让出度为0的点指向入度为0的点,那么只要max(din,dout)即可

AC代码:

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e4+10;
int dfn[N],low[N],tot;
bool instk[N];
int scc[N],cnt;
int din[N],dout[N];//SCC的入度,出度
int n;
vector<vector<int>>e(N);
stack<int>q;
void tarjan(int x){
    dfn[x]=low[x]=++tot;
    q.push(x);
    instk[x]=true;
    for(auto v:e[x]){
        if(!dfn[v]){
            tarjan(v);
            low[x]=min(low[x],low[v]);
        }
        else if(instk[v]){
            low[x]=min(low[x],dfn[v]);
        }
    }
    if(dfn[x]==low[x]){
        int y;
        cnt++;
        do{
            y=q.top();
            q.pop();
            instk[y]=false;
            scc[y]=cnt;
        }while(y!=x);
    }
}
void solve() {
    cin>>n;
    for(int i=1,a;i<=n;i++){
        cin>>a;
        while(a){
            e[i].push_back(a);
            cin>>a;
        }
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i]) tarjan(i);//一些点可能走不到,即图不连通
    }
    for(int x=1;x<=n;x++){//枚举n个点
        for(int y:e[x]){//枚举点x的邻边,x指向y
            if(scc[x]!=scc[y]){//如果x和y所在的强连通分量不一样,即不在同一个强连通分量之内
                din[scc[y]]++;//y所在的强连通分量的入度++
                dout[scc[x]]++;//x所在的强连通分量的出度++
            }
        }
    }
    int a=0,b=0;
    for(int i=1;i<=cnt;i++){
        if(!din[i]) a++;//a表示缩点后入度为0的个数
        if(!dout[i]) b++;//b表示缩点后出度为0的个数
    }
    cout<<a<<endl;
    if(cnt==1) cout<<0<<endl;//特判,如果只有一个强连通分量,那么就不用加边了
    else cout<<max(a,b)<<endl;
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
//    cin>>t;
    while(t--) {
        solve();
    }
    return 0;
}

[USACO03FALL / HAOI2006] 受欢迎的牛 G - 洛谷 

 AC代码:

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e4+10;
int dfn[N],low[N],tot;
bool instk[N];
int scc[N],siz[N];
int cnt;
int dout[N];
vector<vector<int>>e(N);
stack<int>q;
int n,m;
void tarjan(int x){
    dfn[x]=low[x]=++tot;
    q.push(x);
    instk[x]=true;
    for(auto v:e[x]){
        if(!dfn[v]){
            tarjan(v);
            low[x]=min(low[x],low[v]);
        }
        else if(instk[v]){
            low[x]=min(low[x],dfn[v]);
        }
    }
    if(dfn[x]==low[x]){
        int y;
        cnt++;
        do{
            y=q.top();
            q.pop();
            instk[y]=false;
            scc[y]=cnt;
            siz[cnt]++;
        }while(y!=x);
    }
}
void solve() {
    cin>>n>>m;
    for(int i=0;i<m;i++){
        int a,b;
        cin>>a>>b;
        e[a].push_back(b);
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i]) tarjan(i);
    }
    for(int x=1;x<=n;x++){
        for(auto y:e[x]){
            if(scc[x]!=scc[y]){
                dout[scc[x]]++;
            }
        }
    }
    int sum=0;
    int cnt1=0;
    for(int i=1;i<=cnt;i++){
        if(dout[i]==0){
            sum=siz[i];
            cnt1++;
        }
    }
    if(cnt1>1) sum=0;
    cout<<sum<<endl; 
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
//    cin>>t;
    while(t--) {
        solve();
    }
    return 0;
}

【模板】缩点 - 洛谷

先缩点转化成一个无环图

 

团号逆序是拓扑序,因为我们给强连通分量标号的时候是从1开始标的,于是团号小的在拓扑序的末端,这样从大到小枚举团号即为拓扑序,保证是线性的,这样dp的话才能满足无后效性

AC代码:

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e4+10;
int dfn[N],low[N],tot;
bool instk[N];
int scc[N],cnt;
int w[N],nw[N];
int n,m;
vector<vector<int>>e(N),ne(N);
stack<int>q;
int dp[N];
void tarjan(int x){
    dfn[x]=low[x]=++tot;
    q.push(x);
    instk[x]=true;
    for(auto v:e[x]){
        if(!dfn[v]){
            tarjan(v);
            low[x]=min(low[x],low[v]);
        }
        else if(instk[v]){
            low[x]=min(low[x],dfn[v]);
        }
    }
    if(dfn[x]==low[x]){
        int y;
        cnt++;
        do{
            y=q.top();
            q.pop();
            instk[y]=false;
            scc[y]=cnt;
        }while(y!=x);
    }
}
void solve() {
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>w[i];
    for(int i=0;i<m;i++){
        int a,b;
        cin>>a>>b;
        e[a].push_back(b);
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i]) tarjan(i);
    }
    for(int x=1;x<=n;x++){
        nw[scc[x]]+=w[x];//新点的权值
        for(int y:e[x]){
            if(scc[x]!=scc[y]){
                ne[scc[x]].push_back(scc[y]);
            }
        }
    }//缩点后建拓扑图
    for(int x=cnt;x>=1;x--){
        if(dp[x]==0){//若x为路的起点
            dp[x]=nw[x];
        }
        for(auto y:ne[x]){
            dp[y]=max(dp[y],dp[x]+nw[y]);
        }
    }
    int ans=0;
    for(int i=1;i<=cnt;i++) ans=max(ans,dp[i]);//可能图不连通,有多个强连通分量
    cout<<ans<<endl;
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
//    cin>>t;
    while(t--) {
        solve();
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值