POJ2762 Going from u to v or from v to u

一.原题链接:http://poj.org/problem?id=2762

二.题目大意:给你一个有向图,问能不能在图中任意选2个点u, v,使得可以从u到v或者从v到u。

三.思路:

1.预备知识:

(1)在一个强连通分量里面,任意两点都有双向的路径。这是强连通分量的定义。

(2)求一个图强连通分量方法:

tarjan算法:详情:http://blog.csdn.net/h992109898/article/details/51318135

这题跟里面的tarjan算法不一样,不过差不多,也是利用dfn[]和low[]数组,用来求强连通分量。读完上面的博客,大概了解了dfn[]与low[]数组,这题dfn[]是用来标记搜索顺序,low[]用来标记可以到达的最高层祖先处。

求强连通分量的步骤是:每次进入DFS,直接将当前点进栈,然后扫所有的邻接点,有向图不用考虑是不是刚进来的边(跟回边混淆),没有访问过的,继续搜索,退出来之后取low[u] = min(low[u], low[v]),u为当前节点,v为邻接点。访问过的,说明一定有回边,low[u] = min(low[u], dfn[v])。扫完所有的边之后,如果dfn[u] == low[u],说明以u为根节点的深度优先搜索子树上的结点构成了一个强连通分量,把所有点出栈,缩点(待会再说)。为什么会这样呢?因为u肯定是可以到达子树上的所有结点,因为u是根部,而且每个子结点都可以到达u,因为每个子节点通过若干个回边,能回到u。假设没有结点到达u,那么子结点所回到的祖先结点,会自己变成一颗深度优先搜索树的根部,然后自己先弹栈。注意这里在DFS的时候,不能DFS(u, depth+1),而一定要设一个变量,每次进入DFS就加1,这2者不一样的,因为前者会出现多个dfn[u]的值相同,会产生混淆。(反正我改完就AC了)。然后还是不知道为什么会这样。

2.思路切入:我们可以把每个强连通分量看成一个点,点内中随便找2个点肯定可以互通的(强连通分量定义)。然后通过桥构成一个新图,此时要判断的就是这个新图上任取2点u, v,能不能从u到达v或者从v到达u,记住是或者不是并且。然后其实只要进行一次拓扑排序,拓扑排序过程中如果出现入度为0的点同时有1个以上(不包括一个),则选这2个点就不满足条件,如果这2个点代表2个连通分量,那么选它们之中分别的任意2点都不满足条件,假设它们之间有路径,路径只有一个中间节点t,那么要使得2个点入度同时为0,那么必须先把t删除,因为要把t删除了,t连向2个点之中一个点的出边才会删除,要删除t必须删除另外一个节点,因为这样t的入度才为0,于是矛盾了。用数学归纳法可证明路径有多个节点的情况。

为什么不一开始就拓扑排序,而是要先找连通分量缩点呢?难道强连通分量可以拓扑排序?找得到入度为0的点?

四.代码:

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <vector>
#include <stack>

using namespace std;

const int MAX_N = 1558,
          INF = 0x3f3f3f3f;

int nodeNum, edgeNum, cntId, visNum,
    id[MAX_N], low[MAX_N], dfn[MAX_N];
vector <int> edge[MAX_N], tree[MAX_N];
bool visited[MAX_N], inStack[MAX_N];
stack <int> st;

void init()
{
    int i;
    visNum = 0;
    for(i = 0; i < 1024; i++)
        edge[i].clear(), tree[i].clear();
    memset(visited, 0, sizeof(visited));
    memset(inStack, 0, sizeof(inStack));
    memset(tree, 0, sizeof(tree));
    while(!st.empty())
        st.pop();
    cntId = 0;
}

void dfs(int u)
{
    visited[u] = true;
    low[u] = dfn[u] = visNum++;
    st.push(u);
    inStack[u] = true;
    int i, v;
    for(i = 0; i < edge[u].size(); i++){
        v = edge[u][i];
        if(!visited[v]){
            dfs(v);
            low[u] = min(low[u], low[v]);
        }
        else if(inStack[v])
            low[u] = min(low[u], dfn[v]);
    }
    if(low[u] == dfn[u] && inStack[u]){
        int tmp;
        do{
            tmp = st.top();
            st.pop();
            inStack[tmp] = false;
            id[tmp] = cntId;
        }while(tmp != u);
        cntId++;
    }
}

bool topoSort()
{
    int u, i, v, k, cnt[MAX_N], j, cntIn, save;
    bool mark[MAX_N];
    memset(cnt, 0, sizeof(cnt));
    memset(mark, 0, sizeof(mark));
    for(i = 0; i < cntId; i++)
        for(j = 0; j < tree[i].size(); j++)
                cnt[tree[i][j]]++;

    cntIn = 0;
    for(i = 0; i < cntId; i++)
        if(0 == cnt[i]){
            cntIn++;
            save = i;
        }
    if(cntIn > 1)
        return false;
    for(k = 0; k < cntId; k++){
        cntIn = 0;
        u = save;
        for(i = 0; i < tree[u].size(); i++){
            v = tree[u][i];
            cnt[v]--;
            if(0 == cnt[v]){
                cntIn++;
                save = v;
            }
        }
        if(cntIn > 1)
            return false;
    }

    return true;
}

bool judge()
{
    int i, j, v;
    for(i = 1; i <= nodeNum; i++)
        if(!visited[i])
            dfs(i);

    for(i = 1; i <= nodeNum; i++){
        for(j = 0; j < edge[i].size(); j++){
            v = edge[i][j];
            if(id[i] != id[v])
                tree[id[i]].push_back(id[v]);
        }
    }

    return topoSort();
}


int main()
{
    //freopen("in.txt", "r", stdin);

    int test, i, j, u, v;
    scanf("%d", &test);
    while(test--){
        init();
        scanf("%d%d", &nodeNum, &edgeNum);
        for(i = 0; i < edgeNum; i++){
            scanf("%d%d", &u, &v);
            edge[u].push_back(v);
        }
        if(judge())
            printf("Yes\n");
        else
            printf("No\n");
    }
    return 0;
}





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值