每次到了晚上都无法静下心来写题目,不如写篇博客,总结一天的学习。
今天一天,首先回顾了昨晚Codeforces的几道题目。恕本蒟蒻太菜,实在无法写出来后面两道题目。
然后偶然发现很久之前的一道题目还没写,就顺手写了。然后便开始了今天的学习。
今天主要学习的问题是连通图的一些问题。
第一个问题就是强连通分量的求解。这一部分事实上我看了挺多的博客还有资料都没有看的太懂,最后跟着某篇博客里面的模板敲了一遍Tarjan算法。算是稍微懂了一些。
对于强连通分量的问题,本来我只能够用最暴力的方法写出来,是不是正确的还不好说,但是时间复杂度确实是相当的高的。而Tarjan算法,能够在O(V+E)的时间复杂度里面求解出强连通分量。不得不说真是非常神奇。
说起来强联通分量的问题也是深度优先搜索的一个应用吧。
设两个数组,dfn[i]和low[i]分别表示图的第i个点被访问的次序和第i个点能够回退到的最小的一个点。
关于这部分的解释这个博客说的的确还是很可以的。最起码我大概是理解了一些...
然后通过dfs搜索,就可以求出来强连通分量。
然后就是题目,今天做了两题关于强连通分量的题目,
一题是hdu 1269。
中文题直接上代码了。
#include <stack> #include <vector> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 10005; const int maxm = 100005; typedef struct node { int to, nxt; node(int a = 0, int b = 0) { to = a; nxt = b; } }Edge; stack<int> s; //存储已遍历的节点 Edge edge[maxm]; //链式前向星存储边的信息 int tot, head[maxn]; //链式前向星存储边的信息 int InComponet[maxn]; //记录每个点在哪个强联通分量中 vector<int> Componet[maxn]; //记录强联通分量结果 int n, m, index, ComponetNumber; //点的数量、边的数量、索引号、强联通分量数目 int dfn[maxn], low[maxn], inStack[maxn]; //深度优先搜索访问次序、能追溯到的最早的次序、检查是否在栈中(0代表不在,1代表访问过且不在栈中,2代表在栈中) void add(int u, int v) { edge[tot] = Edge(v, head[u]); head[u] = tot++; } void init() { tot = 0; while (!s.empty()) s.pop(); memset(dfn, 0, sizeof(dfn)); memset(low, 0, sizeof(low)); memset(head, -1, sizeof(head)); for (int i = 0; i < maxn; ++i) Componet[i].clear(); index = ComponetNumber = 0; } void tarjan(int u) { s.push(u); inStack[u] = 2; low[u] = dfn[u] = ++index; for (int i = head[u]; ~i; i = edge[i].nxt) { int v = edge[i].to; if (!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); } else if(inStack[v] == 2) { low[u] = min(low[u], dfn[v]); } } if (low[u] == dfn[u]) { ++ComponetNumber; while (!s.empty()) { int v = s.top(); s.pop(); inStack[v] = 1; Componet[ComponetNumber].push_back(v); InComponet[v] = ComponetNumber; if (v == u) break; } } } int main() { ios::sync_with_stdio(false); cin.tie(0); int a, b; while (cin >> n >> m) { if (!n && !m) break; init(); for (int i = 0; i < m; ++i) { cin >> a >> b; add(a, b); } for (int i = 1; i <= n; ++i) { if (!dfn[i]) tarjan(i); } if (ComponetNumber > 1) cout << "No" << endl; else cout << "Yes" << endl; } return 0; }模板题不多说了
一题是poj 1236。
给出n个学校和一些学校之间的网络链接关系,学校之间的网络是单向边,让你求出两个问题的答案,1.至少需要多少份软件,使得所有学校都可以收到。2.如果希望用一份软件就能够使所有学校收到需要添加几条边。
代码:
#include <stack> #include <vector> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 105; const int maxm = maxn * maxn; typedef struct node { int to, nxt; node(int a = 0, int b = 0) { to = a; nxt = b; } }Edge; stack<int> s; //存储已遍历的节点 Edge edge[maxm]; //链式前向星存储边的信息 int tot, head[maxn]; //链式前向星存储边的信息 int InComponet[maxn]; //记录每个点在哪个强联通分量中 int in[maxn], out[maxn]; //记录缩点后各个强连通分量的出度和入度 vector<int> Componet[maxn]; //记录强联通分量结果 int n, m, index, ComponetNumber; //点的数量、边的数量、索引号、强联通分量数目 int dfn[maxn], low[maxn], inStack[maxn]; //深度优先搜索访问次序、能追溯到的最早的次序、检查是否在栈中(0代表不在,1代表访问过且不在栈中,2代表在栈中) void add(int u, int v) { edge[tot] = Edge(v, head[u]); head[u] = tot++; } void init() { tot = 0; while (!s.empty()) s.pop(); memset(in, 0, sizeof(in)); memset(out, 0, sizeof(out)); memset(dfn, 0, sizeof(dfn)); memset(low, 0, sizeof(low)); memset(head, -1, sizeof(head)); for (int i = 0; i < maxn; ++i) Componet[i].clear(); index = ComponetNumber = 0; } void tarjan(int u) { s.push(u); inStack[u] = 2; low[u] = dfn[u] = ++index; for (int i = head[u]; ~i; i = edge[i].nxt) { int v = edge[i].to; if (!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); } else if(inStack[v] == 2) { low[u] = min(low[u], dfn[v]); } } if (low[u] == dfn[u]) { ++ComponetNumber; while (!s.empty()) { int v = s.top(); s.pop(); inStack[v] = 1; Componet[ComponetNumber].push_back(v); InComponet[v] = ComponetNumber; if (v == u) break; } } } int main() { ios::sync_with_stdio(false); cin.tie(0); int a; while (cin >> n) { init(); for (int i = 1; i <= n; ++i) while (cin >> a && a) add(i, a); 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; j = edge[j].nxt) { int v = edge[j].to; if (InComponet[i] != InComponet[v]) { ++in[InComponet[v]]; ++out[InComponet[i]]; } } } int mx1 = 0, mx2 = 0; for (int i = 1; i <= ComponetNumber; ++i) { if (!in[i]) ++mx1; if (!out[i]) ++mx2; } if (ComponetNumber == 1) { cout << "1 0 "; } else { cout << mx1 << endl << max(mx1, mx2) << endl; } } return 0; }解题思路:首先求出这个图的强连通分量,并且缩点。缩点是说把一个强连通分量看成是一个点,然后不同的强连通分量构成了一幅图。对于构成的图,求出每个强连通分量的入度和出度。第一问的解就是入度为0的点的个数,第二问的解就是入度为0的点的个数和出度为0的点的个数最大的值。
学习的第二个问题是关于割点的,给出一幅图,求出割点的个数。
算法大体上跟Tarjan算法差不多,还是一个dfn数组一个low数组。表示的信息也是差不多的。
但是想要找到割点,首先需要知道深度优先搜索生成树。(一脸懵逼
这部分嘛。推荐王桂平的《图论算法理论、实现及应用》里面的介绍。
在找到深度优先搜索生成树之后,只需要找到是生成树的根节点且孩子个数>=2的节点,或者不是生成树的根节点,但是删除这个节点之后,任意一个该节点的孩子节点无法到达该节点的祖先节点。
知道这个判定之后,就可以根据dfn数组和low数组求解了。
这个问题目前只做了一道题目。
UVA 315 Network
题目大意:就是求割点的个数。
代码:
#include <stack> #include <cstdio> #include <vector> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef struct node { int v, nxt; node(int a = 0, int b = 0) { v = a; nxt = b; } }Edge; const int maxn = 105; const int maxm = 2 * maxn * maxn; int inx; Edge edge[maxm]; bool judge[maxn]; int tot, head[maxn]; int low[maxn], dfn[maxn]; void add(int u, int v) { edge[tot] = Edge(v, head[u]); head[u] = tot++; edge[tot] = Edge(u, head[v]); head[v] = tot++; } void init() { tot = inx = 0; memset(low, 0, sizeof(low)); memset(dfn, 0, sizeof(dfn)); memset(head, -1, sizeof(head)); memset(judge, false, sizeof(judge)); } void tarjan(int x, int pre) { int v, ret = 0; dfn[x] = low[x] = ++inx; for (int i = head[x]; ~i; i = edge[i].nxt) { v = edge[i].v; if (v == pre) continue; if (!dfn[v]) { ++ret; tarjan(v, x); low[x] = min(low[x], low[v]); if (x != pre && low[v] >= dfn[x]) judge[x] = true; } else if (low[x] > dfn[v]) { low[x] = dfn[v]; } } if (x == pre && ret > 1) judge[x] = true; } int main() { char op; int a, b, n; while (~scanf("%d", &n) && n) { init(); while (~scanf("%d", &a) && a) { while (~scanf("%d%c", &b, &op)) { add(a, b); if (op == ' ') break; } } for (int i = 1; i <= n; ++i) { if (!dfn[i]) tarjan(i, i); } int ans = 0; for (int i = 1; i <= n; ++i) { if (judge[i]) ++ans; } cout << ans << " "; } return 0; }
解题思路:就是求割点的模板题目,直接套模板就好。
另外还有一个求无向图的割边问题,但是因为到了晚上,没有去学习,所以今天就不再说了。明天学习之后再加上。