• 连通图问题入门小结


    每次到了晚上都无法静下心来写题目,不如写篇博客,总结一天的学习。

    今天一天,首先回顾了昨晚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;
    }

    解题思路:就是求割点的模板题目,直接套模板就好。


    另外还有一个求无向图的割边问题,但是因为到了晚上,没有去学习,所以今天就不再说了。明天学习之后再加上。

  • 相关阅读:
    readAsDataURL(file) & readAsText(file, encoding)
    MySQL: Integer & String types
    JavaScript 中事件绑定的三种方式
    vue-router 导航守卫
    js 常见数组算法
    CSS渐变色边框,解决border设置渐变后,border-radius无效的问题
    margin:auto你真的理解么
    当margin和padding的值是百分比时,如何计算
    关于 js 函数参数的this
    Vue.js 中的 v-cloak 指令
  • 原文地址:https://www.cnblogs.com/wiklvrain/p/8179379.html
Copyright © 2020-2023  润新知