#POJ 2762 Going from u to v or from v to u?(tarjan + 拓扑排序 + 链式前向星)

Going from u to v or from v to u?
Time Limit: 2000MS

Memory Limit: 65536K
Total Submissions: 20770

为了让他们的儿子勇敢,嘉嘉和风把他们带到一个大山洞。这个洞穴有N个房间,还有连接一些房间的单向走廊。每次,风都会选择两个房间x和y,并要求他们的一个小儿子从一个房间到另一个房间。儿子可以从X到Y,也可以从Y到X。Wind承诺她的任务都是可能的,但实际上她不知道如何决定任务是否可能。为了让她的生活更轻松,贾佳决定选择一个洞穴,在那里每对房间都是一个可能的任务。给定一个山洞,你能告诉佳佳风是否可以随意选择两个房间而不必担心什么吗?            

 输入 :  第一行包含一个整数t,即测试用例的数量。并跟踪T案。  每种情况的第一行包含两个整数n,m(0<n<1001,m<6000),洞穴中房间和走廊的数量。下一条M线分别包含两个整数u和v,表示有一条走廊直接连接u和v房间。           

输出应包含T行。如果洞穴具有上述财产,则写“是”,否则写“否”。 

 


Sample Input
1
3 3
1 2
2 3
3 1
Sample Output
Yes

题目大意 :输入一个有向图,判断这个图中任意两点X和Y是否能够从X到Y或者从Y到X

 

A了这道题以后感悟真的太多了。。。废话不多说,讲讲我做题时候的思路:

我们首先缩点,这样图就成了DAG,会简单很多,然后想一想判定条件,如果两个点互相可以到达,或者其中之一到达另一个,注意是或者!!!就比如两个人走路一样,要想他们相遇,一定是一个不动,另一个走向他,或者二者都走向对方,所以,如果他们都从不同的地方同时走向第三者,就满足了路程中无法相遇的情况。

这样说的话是不是有点头绪了,在图论中,有什么算法是可以是可以研究点的方向和顺序的?就是拓扑排序!在拓扑排序运行的过程中,如果出现某一步有两个点的入度都为0,就会有    “两个人从不同方向走向第三者” 的情况,不知道这样描述好不好理解哈,我就是这么想的QAQ,说到这,应该已经知道怎么做了吧~

 

语文不好,怕你们看不懂,再简单说下代码怎么写吧 !

先用链式前向星存图(链式前向星相比于邻接表要快,只是写起来麻烦),tarjan缩点,然后将缩完的点再存到新的图中,拓扑排序判断入度为0的点是否超过1个,如果是,则输出No,否则输出Yes

由于忘了初始化,WA了两次 T_T

下面是AC代码 :

#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<cstring>
using namespace std;
const int maxn = 1e5 + 5;

struct node
{
    int v, next; 
}e[maxn], d[maxn];   //e存缩点前的图,d存缩点后的图
int dfn[maxn], low[maxn], in[maxn], tot, n, m, oth;//in是入度,dfn、low、vis是tarjan的
int head[maxn], hed[maxn], cnt, T, suo[maxn], scnt; // head是第一张图,hed是第二张图
bool vis[maxn], flag; 
stack <int> st; //tarjan存点
void init() {
    memset(e, 0, sizeof(e));
    memset(d, 0, sizeof(d));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(vis, 0, sizeof(vis));
    memset(head, -1, sizeof(head));
    memset(suo, 0, sizeof(suo));
    memset(hed, -1, sizeof(hed));
    memset(in, 0, sizeof(in));
    cnt = tot = scnt = oth = 0;
    flag = 1;
}
void add (int from, int to) {
    e[++cnt].v = to;
    e[cnt].next = head[from];
    head[from] = cnt;
}
void ad (int from, int to) {
    d[++oth].v = to;
    d[oth].next = hed[from];
    hed[from] = oth;
}
void tarjan (int u) {
    dfn[u] = low[u] = ++tot;
    vis[u] = 1;
    st.push(u);
    for (int i = head[u]; i != -1; i = e[i].next) {
        if (!dfn[e[i].v]) {
            tarjan (e[i].v);
            low[u] = min (low[u], low[e[i].v]);
        }
        else if (vis[e[i].v]) low[u] = min (low[u], dfn[e[i].v]);
    }
    if (dfn[u] == low[u]) {
        scnt++;
        int k, sum = 0;
        do {
            sum++;
            k = st.top();
            suo[k] = scnt; //缩点
            st.pop();
            vis[k] = 0;
        }
        while (k != u);
        return;
    }
}
void topsort() {
    queue <int> q;
    int ans = 0;
    for (int i = 1; i <= scnt; i++) {
        if (!in[i]) q.push(i), ans++;
    }
    if (ans > 1) {flag = 0;return;}   //看第一次弹出的时候入度为0的点有几个
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        int sum = 0;
        for (int i = hed[u]; i != -1; i = d[i].next) {
            int v = d[i].v;
            in[v]--;
            if (!in[v]) q.push(v), sum++;
        }
        if (sum > 1) {flag = 0;return;} //第一次之后的入度为0的有几个
    }
}

int main()
{
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);
        init();
        for (int i = 0; i < m; i++) {
            int ui, vi;
            scanf("%d%d", &ui, &vi);
            add (ui, vi);
        }
        for (int i = 1; i <= n; i++) {
            if (!dfn[i]) tarjan (i);
        }
        for (int i = 1; i <= n; i++) {
            for (int j = head[i]; j != -1; j = e[j].next) {
                if (suo[i] != suo[e[j].v]) ad (suo[i], suo[e[j].v]), in[suo[e[j].v]]++;
            }   //如果缩点前的点和缩点后的点不在一起,就要把他们缩点后的集合之间的关系表示出来
        }
        topsort(); //拓扑排序
        if (flag) cout << "Yes" << endl;
        else cout << "No" << endl;
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值