連通圖問題入門小結

每次到了晚上都無法靜下心來寫題目,不如寫篇博客,總結一天的學習。

今天一天,首先回顧了昨晚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\n0\n";
		} 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 == '\n') 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 << "\n";
	}
	return 0;
}

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


另外還有一個求無向圖的割邊問題,但是因爲到了晚上,沒有去學習,所以今天就不再說了。明天學習之後再加上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章