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;
}