tarjan算法-缩点

有向图的缩点就是把有向图中强连通分量缩成一个点(道理很简单,我到了这个强连通分量的任何一点,那么这个强连通分量上的点就都能被我访问了),在处理有向图的连通性问题时有很多作用。

代码是对求连通分量的改的,cnt做连通量的编号,belong[],表示点属于哪个连通分量,vector<> p存储每个连通量的点。

 

int Stack[maxn], low[maxn], dfn[maxn], inStack[maxn], belong[maxn];
int now,  cnt; // now:时间戳,cnt强连通的个数
vector<int> g[maxn];
stack<int> s;
vector<int> p[maxn];void init(){
    now = cnt = 0;
    memset(inStack, 0,sizeof(inStack));
    memset(belong, 0,sizeof(belong));
    memset(dfn, 0,sizeof(dfn));
    memset(low, 0,sizeof(low));
    memset(out, 0,sizeof(out));
    for(int i=0;i<maxn;i++) {
        g[i].clear();
        p[i].clear();
    }
}
void tarjan(int u){
    // 打上标记,入栈
    low[u] = dfn[u] = ++now;
    s.push(u);
    inStack[u] = 1;
    for (int i = 0; i < (int)g[u].size(); ++i) {
        int v = g[u][i];
        if (!dfn[v]) { //未访问过 
            tarjan(v);
            low[u] = min(low[u], low[v]); //low,dfn(v)中可能是最小的 
        }
        else if (inStack[v]) { //访问过,还在栈中 
            low[u] = min(low[u], dfn[v]); //dfn (v)中找最小 
        }
    }
    if (dfn[u] == low[u]) {
        ++cnt; // 统计个数
        int v=-1;
        while (v!= u) {
            v = s.top();
            s.pop();
            belong[v] = cnt;
            inStack[v] = 0; 
            p[cnt].push_back(v);
        }
    }
}

 

习题

(1.

给出一个 0 ≤ N ≤ 105 点数、0 ≤ M ≤ 105 边数的有向图,

输出一个尽可能小的点集,使得从这些点出发能够到达任意一点,如果有多个这样的集合,输出这些集合升序排序后字典序最小的。

第一行为两个整数 1 ≤ n, m ≤ 105,
接下来 M 行,每行两个整数 1 ≤ u, v ≤ 105 表示从点 u 至点 v 有一条有向边。

数据保证没有重边、自环。

第一行输出一个整数 z,表示作为答案的点集的大小;

第二行输出 z 个整数,升序排序,表示作为答案的点集。

解析

首先输出的点集,应该是有每个缩点中的任意一个点组成的;如果一个a缩点内的一点能够到达另一个b缩点内的点,说明缩点b可以通过缩点a到达,b就不需要考虑了.

依照这两点我们就可以构造一个数组link[]:

for() //遍历每个u
    for() //遍历边u-v
        if()  //如果u和v不属于同一个强连通量
            link[belong[v]]=true //包含v的连通量成无关

这里可能会想如果v的连通分量有另一点可以到u的分量,会不会造成两个连通量都遮蔽了。其实不会,如果存在这样的点,那么这两个连通量也变成了一个大的连通量,但是tarjan得到的就是最大的强连通量

c++代码

#include<bits/stdc++.h>
using namespace std;
 
const int maxn=1e5+100;
int low[maxn], dfn[maxn], inStack[maxn], belong[maxn],link[maxn] ;
int now,  cnt; // now:时间戳,cnt强连通的个数
vector<int> g[maxn];
stack<int> s;
vector<int> p[maxn];
vector<int> ans;
void init(){
    now = cnt = 0;
    memset(inStack, 0,sizeof(inStack));
    memset(belong, 0,sizeof(belong));
    memset(dfn, 0,sizeof(dfn));
    memset(low, 0,sizeof(low));
    memset(link, 0,sizeof(link));
    for(int i=0;i<maxn;i++) {
        g[i].clear();
        p[i].clear();
    }
    ans.clear();
}
void tarjan(int u){
    // 打上标记,入栈
    low[u] = dfn[u] = ++now;
    s.push(u);
    inStack[u] = 1;
    for (int i = 0; i < (int)g[u].size(); ++i) {
        int v = g[u][i];
        if (!dfn[v]) { //未访问过 
            tarjan(v);
            low[u] = min(low[u], low[v]); //low,dfn(v)中可能是最小的 
        }
        else if (inStack[v]) { //访问过,还在栈中 
            low[u] = min(low[u], dfn[v]); //dfn (v)中找最小 
        }
    }
    if (dfn[u] == low[u]) {
        ++cnt; // 统计个数
        int v=-1;
        while (v!= u) {
            v = s.top();
            s.pop();
            belong[v] = cnt;
            inStack[v] = 0; 
            p[cnt].push_back(v);
        }
    }
}
int main(){
    init();
    int n,m;
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++) {
        int u,v;
        scanf("%d%d",&u,&v);
        g[u].push_back(v);
    }
    for (int i=1;i<=n;i++) {
        if (!dfn[i]) tarjan(i);
    }
    for(int u=1;u<=n;u++){
           for(int j=0;j<g[u].size();j++){
                   int v=g[u][j];
                   if(belong[u]!=belong[v]){
                           link[belong[v]]=1;
                   }
           }
    }
    for (int i=1;i<=cnt;i++) sort(p[i].begin(),p[i].end());
    for (int i=1;i<=cnt;i++) {
        if (link[i]==0) ans.push_back(p[i][0]);//选取第一个点就好了
    }
    sort(ans.begin(),ans.end());
    printf("%d\n",ans.size());
    for (int i=0;i<ans.size();i++) {
        printf("%d%c",ans[i],(i==ans.size()-1)?'\n':' ');
    }
    return 0;
}
 

 

(2

Every cow's dream is to become the most popular cow in the herd. In a herd of N (1 <= N <= 10,000) cows, you are given up to M (1 <= M <= 50,000) ordered pairs of the form (A, B) that tell you that cow A thinks that cow B is popular. Since popularity is transitive, if A thinks B is popular and B thinks C is popular, then A will also think that C is 
popular, even if this is not explicitly specified by an ordered pair in the input. Your task is to compute the number of cows that are considered popular by every other cow. 

Input

* Line 1: Two space-separated integers, N and M 

* Lines 2..1+M: Two space-separated numbers A and B, meaning that A thinks B is popular. 

Output

* Line 1: A single integer that is the number of cows who are considered popular by every other cow. 

Sample Input

3 3
1 2
2 1
2 3

Sample Output

1

Hint

Cow 3 is the only cow of high popularity. 
 
解析
首先缩点,内部能形成互相欣赏的牛为一个缩点(互吹),只要求一个能被所有缩点达到的点。对于能被所有点到达(成为目标点)和不能被所有牛(贡献点)到达的点,区别就是目标点内的点不可能都有一条边指向贡献点中的同一点(如果是的话,这个点也在目标了)。故贡献点的出度为0。
且一个有一个缩点出度为0。假设有两个,这两个点都不能满足被所有缩点到达,而且这也违反了我们创建目标点的意图。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<vector>
#include<stack> 
using namespace std;
const int maxn=2010;
int  low[maxn], dfn[maxn], inStack[maxn], belong[maxn];
int now, cnt; // now:时间戳,cnt强连通的个数
vector<int> g[maxn];
stack<int> s;
void init() {
    now = cnt = 0;
    memset(inStack, 0,sizeof(inStack));
    memset(belong, 0,sizeof(belong));
    memset(dfn, 0,sizeof(dfn));
    memset(low, 0,sizeof(low));
    for(int i=0;i<maxn;i++) g[i].clear();
}
void tarjan(int u) {
    // 打上标记,入栈
    low[u] = dfn[u] = ++now;
    s.push(u);
    inStack[u] = 1;
    for (int i = 0; i < (int)g[u].size(); ++i) {
        int v = g[u][i];
        if (!dfn[v]) { //未访问过 
            tarjan(v);
            low[u] = min(low[u], low[v]); //low,dfn(y)中可能是最小的 
        }
        else if (inStack[v]) { //访问过,还在栈中 
            low[u] = min(low[u], dfn[v]); //dfn 
           }
    }
    // 回溯,如果当前节点的dfn = low 表示栈中形成一个强连通分量
    if (dfn[u] == low[u]) {
        ++cnt; // 统计个数
        int v=-1;
        while (v!= u) {
            v = s.top();
            s.pop();
            belong[v] = cnt;
            inStack[v] = 0;
        }
    }
}
int main(){
     int n,m;
     while(scanf("%d%d",&n,&m)!=EOF){
         init();
         if(n==0&&m==0) break;
         int f1,f2,p1,p2;
         for(int i=1;i<=m;i++){
             scanf("%d%d",&f1,&f2);
            scanf("%d%d",&p1,&p2);
            p1=f1*2+p1,p2=f2*2+p2;
             g[p1].push_back(p2^1);
             g[p2].push_back(p1^1);
         }
         for(int i=0;i<2*n;i++){
             if(!dfn[i]) tarjan(i);
         } 
         int flag=0;
//         for(int i=0;i<2*n;i++) cout<<belong[i]<<endl;
         for(int i=0;i<2*n;i+=2){
             if(belong[i]==belong[i^1]) {
                 flag=1;
                 break;
             }
         }
         if(!flag) cout<<"YES"<<endl;
         else cout<<"NO"<<endl;
     }
}

 

转载于:https://www.cnblogs.com/jjl0229/p/11310736.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值