题目链接
题意
判定一个图是不是单向连通图。
// 其实就是poj 2186,不过poj的那道题数据水了些= =
// 浏览题目时看成了FFT at Valentine吓死我= =
思路
先套路一发,tarjan求强联通分量,缩点,至此预处理完成。(这部分详细内容烦请移步本菜另一篇 强联通分量 缩点 tarjan 入门题小集)
然后怎么处理呢?
法一
现在我们得到了一个 DAG ,直观想法就是有没有一个长链串起了图中所有的点。于是可以模仿一下树形 dp 的做法, dfs 一遍, dep[u]=max{dep[v]+1|∀v,u→v} .
Code
#include <cstdio>
#include <cstring>
#define maxn 1010
#define maxm 6010
#include <stack>
#define inf 0x3f3f3f3f
using namespace std;
stack<int> s;
int dfn[maxn], low[maxn], ne[maxn], ne2[maxn], belong[maxn], ind[maxn], tot, tot2, cnt, scc, in[maxn], kas, dep[maxn], vis[maxn];
bool ok;
struct Edge {
int from, to, ne;
Edge(int a = 0, int b = 0, int c = 0) : from(a), to(b), ne(c) {}
}edge[maxm], edge2[maxm];
void add(int u, int v) {
edge[tot] = Edge(u, v, ne[u]);
ne[u] = tot++;
}
void add2(int u, int v) {
edge2[tot2] = Edge(u, v, ne2[u]);
ne2[u] = tot2++;
}
void init() {
tot = 0;
memset(ne, -1, sizeof(ne));
}
void init2() {
tot2 = 0; ok = false;
memset(vis, 0, sizeof(vis));
memset(ne2, -1, sizeof(ne2));
memset(dep, 0, sizeof(dep));
}
void tarjanInit() {
cnt = scc = 0;
while (!s.empty()) s.pop();
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
memset(ind, 0, sizeof(ind));
memset(in, 0, sizeof(in));
}
void tarjan(int u) {
dfn[u] = low[u] = ++cnt;
in[u] = true;
s.push(u);
for (int i = ne[u]; i != -1; i = edge[i].ne) {
int v = edge[i].to;
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if (in[v]) low[u] = min(low[u], dfn[v]);
}
if (low[u] == dfn[u]) {
++scc;
while (true) {
int v = s.top();
in[v] = false;
s.pop();
belong[v] = scc;
if (v == u) break;
}
}
}
void contract() {
init2();
for (int i = 0; i < tot; ++i) {
int u = edge[i].from, v = edge[i].to;
if (belong[u] == belong[v]) continue;
add2(belong[u], belong[v]);
++ind[belong[v]];
}
}
void dfs(int u) {
vis[u] = true;
dep[u] = 1;
for (int i = ne2[u]; i != -1; i = edge2[i].ne) {
int v = edge2[i].to;
if (!vis[v]) dfs(v);
dep[u] = max(dep[u], dep[v] + 1);
}
}
void work() {
int n, m;
init(); tarjanInit();
scanf("%d%d", &n, &m);
for (int i = 0; i < m; ++i) {
int u, v;
scanf("%d%d", &u, &v);
add(u, v);
}
for (int i = 1; i <= n; ++i) {
if (!dfn[i]) tarjan(i);
}
contract();
for (int i = 1; i <= scc; ++i) {
if (!ind[i]) { dfs(i); if (dep[i] == scc) ok = true; break; }
}
if (ok) printf("I love you my love and our love save us!\n");
else printf("Light my fire!\n");
}
int main() {
int T;
scanf("%d", &T);
while (T--) work();
return 0;
}
法二
在新图中,必定只能有一个入度为
0
的点,否则走了一个,另一个就没办法达到了。
删去这个点和它指向的边,图中剩下的部分中也必定只能有一个入度为
依次类推。
咦?这不就是拓扑排序吗!
Code
#include <cstdio>
#include <cstring>
#define maxn 1010
#define maxm 6010
#include <stack>
#include <queue>
#define inf 0x3f3f3f3f
using namespace std;
stack<int> s;
queue<int> q;
int dfn[maxn], low[maxn], ne[maxn], ne2[maxn], belong[maxn], ind[maxn], tot, tot2, cnt, scc, in[maxn], kas, dep[maxn], vis[maxn];
struct Edge {
int from, to, ne;
Edge(int a = 0, int b = 0, int c = 0) : from(a), to(b), ne(c) {}
}edge[maxm], edge2[maxm];
void add(int u, int v) {
edge[tot] = Edge(u, v, ne[u]);
ne[u] = tot++;
}
void add2(int u, int v) {
edge2[tot2] = Edge(u, v, ne2[u]);
ne2[u] = tot2++;
}
void init() {
tot = 0;
memset(ne, -1, sizeof(ne));
}
void init2() {
tot2 = 0;
memset(vis, 0, sizeof(vis));
memset(ne2, -1, sizeof(ne2));
memset(dep, 0, sizeof(dep));
}
void tarjanInit() {
cnt = scc = 0;
while (!s.empty()) s.pop();
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
memset(ind, 0, sizeof(ind));
memset(in, 0, sizeof(in));
}
void tarjan(int u) {
dfn[u] = low[u] = ++cnt;
in[u] = true;
s.push(u);
for (int i = ne[u]; i != -1; i = edge[i].ne) {
int v = edge[i].to;
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if (in[v]) low[u] = min(low[u], dfn[v]);
}
if (low[u] == dfn[u]) {
++scc;
while (true) {
int v = s.top();
in[v] = false;
s.pop();
belong[v] = scc;
if (v == u) break;
}
}
}
void contract() {
init2();
for (int i = 0; i < tot; ++i) {
int u = edge[i].from, v = edge[i].to;
if (belong[u] == belong[v]) continue;
add2(belong[u], belong[v]);
++ind[belong[v]];
}
}
bool topsort() {
int zero = 0, idx;
for (int i = 1; i <= scc; ++i) {
if (ind[i] == 0) ++zero, idx = i;
}
if (zero > 1) return false;
while (!q.empty()) q.pop();
q.push(idx);
while (!q.empty()) {
int u = q.front(); q.pop();
zero = 0;
for (int i = ne2[u]; i != -1; i = edge2[i].ne) {
int v = edge2[i].to;
--ind[v];
if (ind[v] == 0) ++zero, idx = v;
}
if (zero > 1) return false;
if (zero == 1) q.push(idx);
}
return true;
}
void work() {
int n, m;
init(); tarjanInit();
scanf("%d%d", &n, &m);
for (int i = 0; i < m; ++i) {
int u, v;
scanf("%d%d", &u, &v);
add(u, v);
}
for (int i = 1; i <= n; ++i) {
if (!dfn[i]) tarjan(i);
}
contract();
if (topsort()) printf("I love you my love and our love save us!\n");
else printf("Light my fire!\n");
}
int main() {
int T;
scanf("%d", &T);
while (T--) work();
return 0;
}

本文介绍了两种判定单向连通图的方法:一种是通过树形dp的方式寻找长链是否串起所有点;另一种则是利用拓扑排序的思想,判断是否存在唯一的起点。文中提供了完整的代码示例。
539

被折叠的 条评论
为什么被折叠?



