「Tarjan」有向图强连通分量

内容

  • tarjan求解有向图强连通分量
  • tarjan求无向图割点与桥,
  • tarjan解决2-SAT问题

Tarjan求有向图强连通分量

学习资源

一些结论

  • 有向图Tarjan缩点后图为拓扑图且与连通分量相反
  • 有向图转化为强连通分量图需要加 m a x ( p , q ) max(p,q) max(p,q) 条边,其中 p p p为所点后出度为零的点, q q q 为出度为零的点
Template
int n,m;
vector<int> g[50005];
int dfn[50005],low[50005],id[50005];// id 结点在的强连通分量编号
int tim,cnt; // time 时间戳  cnt 连通分量个数
int stk[50005],top;
bool ins[50005];//是否在栈内
int num[50005];// vector<int> v  用来存每个连通分量中的点


void tarjan(int u){
    dfn[u] = low[u] = ++tim;
    stk[++top] = u,ins[u] = 1;
    for(auto v:g[u]){
        if(!dfn[v]){
            tarjan(v);
            low[u] = min(low[u],low[v]);
        }
        else if(ins[v]) low[u] = min(low[u],low[v]);
    }
    if(low[u] == dfn[u]){
        cnt++;int y;
        do{
            y = stk[top--]; id[y] = cnt;
            ins[y] = 0;num[cnt]++;
        }while(u != y);
    }
}

// 有向图这步很重要!
forr(i,1,n) if(!dfn[i]) tarjan(i);


//缩点
// 开一个新的vector来存
  forr(i,1,n){
        for(auto j:g[i]){
            if(id[i]!=id[j]){
                scc[id[i]].push_back(j);
            }
        }
    }

例题

10091. 「一本通 3.5 例 1」受欢迎的牛

给一有向图问有多少个点,其他所有点都能到达它
T a r j a n Tarjan Tarjan 算法裸板子


int out[50005];
signed main()
{
    cin>>n>>m;
    while(m--){
        int a,b;
        cin>>a>>b;
        g[a].push_back(b);
    }

    //  求有向图的联通分量
    forr(i,1,n) if(!dfn[i]) tarjan(i);

    
    forr(i,1,n){
        for(auto v:g[i]){
            if(id[v] != id[i]) out[id[i]]++;
        }
    }
    set<int> v;
    forr(i,1,n) if(!out[id[i]]) v.insert(id[i]);
    if(v.size() > 1) cout << 0 << endl;
    else {
        int t = *v.begin();
        cout << num[t] << endl;
    }
    return 0;
}
10092. 「一本通 3.5 例 2」最大半连通子图

缩点后求最长链以及最长链的个数

#define int long long
#define endl "\n"
#define _orz ios::sync_with_stdio(false),cin.tie(0)
#define mem(str,num) memset(str,num,sizeof(str))
#define forr(i,a,b) for(int i = a; i <= b;i++)
#define forn(i,n) for(int i = 0; i < n; i++)
#define dbg() cout <<"0k!"<< endl;
typedef long long ll;
int pri[16] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
const int inf = 0x3f3f3f3f;
const int INF = ~0ULL;
const int N = 1e6+10;

int n,m,x;
int dfn[100005],low[100005];
int tim;
int id[100005],cnt;
int stk[100005],top;
bool ins[100005];
int num[100005]; //  连通分量的大小
vector<int> g[100005];
vector<int> g2[100005];
map<pair<int,int>,bool> mp;
int f[100005],g1[100005];


void tarjan(int x){
    dfn[x] = low[x] = ++tim;
    stk[++top] = x,ins[x] = 1;
    for(auto v:g[x]){
        if(!dfn[v]){
            tarjan(v);
            low[x] = min(low[x],low[v]);
        }else if(ins[v]){
            low[x] = min(low[x],low[v]);
        }
    }
    if(low[x] == dfn[x]){
        cnt++;int y;
        do{
            y = stk[top--];ins[y] = 0;
            id[y] = cnt;num[cnt]++;
        }while(x!=y);
    }
}

// 类似于求最短路有多少条
void dp(){
    for(int i = cnt;i;i--){
        if(!f[i]){
            f[i] = num[i];
            g1[i] = 1;
        }
        for(auto v:g2[i]){
            if(f[v] < f[i] + num[v]){
                f[v] = f[i]+num[v];
                g1[v] = g1[i];
            }
            else if(f[v] == f[i] + num[v]){
                g1[v] = (g1[v]+g1[i])%x;
            }
        }
    }
}
signed main()
{
    cin>>n>>m>>x;
    while(m--){
        int u,v;cin>>u>>v;
        g[u].push_back(v);
    }
    // 计算连通分量
    forr(i,1,n)  if(!dfn[i]) tarjan(i);
    // 缩点
    forr(i,1,n){
        for(auto v:g[i]){
        	// 每个连通分量只能加一条边
            if(id[i] != id[v] && !mp[{id[i],id[v]}]){
                g2[id[i]].push_back(id[v]);
                mp[{id[i],id[v]}] = 1;
            } 
        }
    }
    // 
    dp();
    int res = -inf,ans;
    // 背包求最大价值方案数
    forr(i,1,cnt){
        if(f[i] > res){
            res  = f[i];
            ans = g1[i];
        }
        else if(f[i]==res){
            ans = (ans+g1[i])%x;
        }
    }
    cout << res << endl;
    cout << ans << endl;
    return 0;
}
10096. 「一本通 3.5 练习 4」抢掠计划

给一张有向图及其一个起点和多个终点,图中每个结点都有权值,问如何走可以使到达一个终点的权值最大,每个点可以多次经过但权值只对答案贡献一次

有向图考虑缩点,对一个连通分量内的点权值相加作为缩点后点的权值,缩完点后从起点所在连通分量开始 d p dp dp 求出到每个终点可以获得的最大权值

#define int long long
#define endl "\n"
#define _orz ios::sync_with_stdio(false),cin.tie(0)
#define mem(str,num) memset(str,num,sizeof(str))
#define forr(i,a,b) for(int i = a; i <= b;i++)
#define forn(i,n) for(int i = 0; i < n; i++)
#define dbg() cout <<"0k!"<< endl;
typedef long long ll;
int pri[16] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
const int inf = 0x3f3f3f3f;
const int INF = ~0ULL;
const int N = 1e6+10;
int n,m;
struct node{
    int to,ne;
}e[500005];

int h[500005],idx = 1;
void add(int u,int v){
    e[++idx] = {v,h[u]};
    h[u] = idx;
}
int w[500005];
int dfn[500005],low[500005],id[500005];// id 结点在的强连通分量编号
int tim,cnt; // time 时间戳  cnt 连通分量个数
int stk[500005],top;
bool ins[500005];//是否在栈内
vector<int> v[500005],g[500005];
int val[500005];
int f[500005];

void tarjan(int u){
    dfn[u] = low[u] = ++tim;
    stk[++top] = u,ins[u] = 1;
    
    for(int i = h[u];i;i=e[i].ne){
        int v = e[i].to;
        if(!dfn[v]){
            tarjan(v);
            low[u] = min(low[u],low[v]);
        }
        else if(ins[v]) low[u] = min(low[u],low[v]);
    }
    if(low[u] == dfn[u]){
        cnt++;int y;
        do{
            y = stk[top--]; id[y] = cnt;
            ins[y] = 0;v[cnt].push_back(y);
        }while(u != y);
    }
}
signed main()
{
    cin>>n>>m;
    while(m--){
        int u,v;cin>>u>>v;
        add(u,v);
    }
    forr(i,1,n) cin>>w[i];
    int s;cin>>s;
    int r;cin>>r;
    vector<int> ed;
    while(r--) {
        int x;cin>>x;
        ed.push_back(x);
    }
    forr(i,1,n) if(!dfn[i]) tarjan(i);
    // 合并权值;
    forr(i,1,n){
        //cout << id[i] << endl;
        int t = id[i];
        val[t] += w[i];
    }
    // 缩点
    forr(i,1,n){
        for(int j =h[i];j;j=e[j].ne){
            int v = e[j].to;
            if(id[v]!=id[i]){
                g[id[i]].push_back(id[v]);
            }
        }
    }
    // dp
    for(int i = id[s];i;i--){
        if(!f[i]){
            f[i] = val[i];
        }
        for(auto v:g[i]){
            if(f[v] < f[i] + val[v]){
                f[v] = f[i]+val[v];
            }
        }
    }
    int res = 0;
    for(auto j:ed){
        res = max(res,f[id[j]]);
    }
    cout << res << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值