A Bug’s Life(poj 2492)
原题链接
题目类型:并查集
解题思路:若存在a、b则代表a和b不位于同一个性别中,因此对于每一个点,建立其对立点a’。当出现a,b时,则代表a和b’ 、a’和b在同一个集合中。最终通过查看a和a’是否在同一个集合中,来判断是否存在异常。
#include<iostream>
using namespace std;
#define NMAX 2200
int num = 1;
int T;
int fa[NMAX * 2];
int find(int x) {
if (x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
int main() {
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
for (int i = 1; i <= 2 * n; i++) fa[i] = i;
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
int A1 = find(a), B2 = find(b + n);
fa[A1] = B2; //注意在每一次对代表元进行操作的时候都应该先进行代表元的查找
int B1 = find(b),A2 = find(a + n);
fa[A2] = B1;
}
//for (int i = 1; i <= n; i++) cout << fa[i] << " " << fa[i + n] << endl;
int key = 0;
for (int i = 1; i <= n; i++) {
if (find(i) == find(i + n)) {
key = 1;
}
}
cout << "Scenario #" << num << ":" << endl;
if (key) cout << "Suspicious bugs found!" << endl << endl;
else cout << "No suspicious bugs found!" << endl << endl;
num++;
}
}
tip;
1.presentation error往往是由于输出格式的错误导致的。
2.OLE错误往往是由于输出了不应该输出的信息造成的。
SPF(poj 1523)
原题链接
题目类型:割点
使用tarjan求割点
如果 p 存在一个子结点 q 满足 low[q]>=dfn[p],说明 q 无法通过它的子树“逃”到比 q 的dfs序更小的节点。那么,既然走子树走不通, q 如果想到达这样的点,只能选择经过它的父节点 p。因此,如果删去 p , q 和dfs序小于p 点的点就分开了。
(注意节点的编号和dfs序之间的关系)
由于上述方法仅仅适用于判断某个点是否为割点,而对于联通分量的计算是错误的,因此需要进行改进。
需要注意的是,为了正确计算删去每一个点后所剩下的连通分量数,应该对于每一个点进行一次dfs。
#include<iostream>
#include<vector>
#include<stack>
#define NMAX 1100
using namespace std;
struct edge {
int from, to;
int next;
};
vector<edge > E;
vector<pair<int, int> > res;
stack<int > S;
int h[NMAX];
int a, b;
int n;
int dfn[NMAX], low[NMAX], inq[NMAX];
int tot = 0;
void addline(int x, int y) {
edge cur;
cur.from = x, cur.to = y, cur.next = h[x];
h[x] = E.size();
E.push_back(cur);
cur.from = y, cur.to = x, cur.next = h[y];
h[y] = E.size();
E.push_back(cur);
}
void tarjan(int x,int key) {
int ret = 0;
dfn[x] = low[x] = ++tot;
inq[x] = 1;
for (int i = h[x]; i != 0; i = E[i].next) {
int to = E[i].to;
if (!dfn[to]) {
tarjan(to, 0);
low[x] = min(low[x], low[to]);
if (low[to] >= dfn[x]) ret++;
}
else if (inq[to]) low[x] = min(low[x], dfn[to]);
}
//仅当其为搜索树的根节点时,才予以计数
if (key && ret > 1) res.push_back(make_pair(x, ret));
}
int main() {
cin >> a;
int num = 0;
while (a) {
//清空所有的变量
memset(h, 0, sizeof(h));
memset(inq, 0, sizeof(inq));
memset(dfn, 0, sizeof(dfn));
res.clear();
while (!S.empty()) S.pop();
tot = 0;
cin >> b;
addline(a, b);
cin >> a;
while (a) {
cin >> b;
if (a > n) n = a;
if (b > n) n = b;
addline(a, b);
cin >> a;
}
for (int i = 1; i <= n; i++) {
//每一个点都应该做一次tarjan算法
memset(inq, 0, sizeof(inq));
memset(dfn, 0, sizeof(dfn));
if (!dfn[i]) tarjan(i,1);
}
num++;
cout << "Network #" << num << endl;
if (!res.size()) cout << " No SPF nodes" << endl;
else {
for (int i = 0; i < res.size(); i++) cout << " SPF node " << res[i].first << " leaves " << res[i].second << " subnets" << endl;
}
cout << endl;
cin >> a;
}
}
在对于tarjan求割点和桥进行进一步的学习后,发现昨日自己的理解出现了问题,因此做出了如下改进。
一些思路的更正
首先,割点和桥是无向连通图中的概念,而最开始使用tarjan来求取有向图的强连通分量,此时有向图的连通性是待求解的。
其次,根的概念是指dfs搜索过程的起点。
然后,对于节点from,其子节点to(注意是dfs搜索过程中的子节点),若low[to]>=dfn[from],则代表to只有通过from才能到达比from搜索序更小的节点,因此断掉边(from,to),会切断from的祖先联通分量和以to为根节点的子连通分量。从上述论述中不难发现,当from的子连通分量有n个,那么再加上祖先联通分量,总共n+1个。
最后需要注意的,由于是无向图的特性,因此to一定是可达from的,但是此时我们不能使用from的dfn来更新to的low值,因此在tarjan过程中需要传入其父节点的编号。
#include<iostream>
#include<vector>
#include<stack>
#include<set>
#define NMAX 1100
using namespace std;
struct edge {
int from, to;
int next;
};
vector<edge > E;
set<pair<int, int> > res;
stack<int > S;
int h[NMAX];
int a, b;
int n;
int dfn[NMAX], low[NMAX], inq[NMAX];
int tot = 0;
set<int> dot;
void addline(int x, int y) {
edge cur;
cur.from = x, cur.to = y, cur.next = h[x];
h[x] = E.size();
E.push_back(cur);
cur.from = y, cur.to = x, cur.next = h[y];
h[y] = E.size();
E.push_back(cur);
}
void tarjan(int x,int fa,int key) {
int ret = 0;
dfn[x] = low[x] = ++tot;
inq[x] = 1;
for (int i = h[x]; i != -1; i = E[i].next) {
int to = E[i].to;
if (!dfn[to]) {
tarjan(to, x,0);
low[x] = min(low[x], low[to]);
if (low[to] >= dfn[x]) ret++;
}
else if (inq[to] && to!=fa) low[x] = min(low[x], dfn[to]);
}
if (key && ret > 1) res.insert(make_pair(x, ret));
else if (!key && ret > 0) res.insert(make_pair(x, ret +1 ));
}
int main() {
cin >> a;
int num = 0;
while (a) {
//清空所有的变量
memset(h, -1, sizeof(h)); //注意边的编号是从1开始的,因此需要设置最初始的值为-1
memset(inq, 0, sizeof(inq));
memset(dfn, 0, sizeof(dfn));
res.clear();
dot.clear();
while (!S.empty()) S.pop();
tot = 0;
cin >> b;
addline(a, b);
cin >> a;
while (a) {
cin >> b;
if (a > n) n = a;
if (b > n) n = b;
addline(a, b);
dot.insert(a);
dot.insert(b);
cin >> a;
}
//for (int i = 0; i < E.size(); i++) cout << E[i].from << " " << E[i].to << endl;
set<int>::iterator it = dot.begin();
for (; it != dot.end();it++) {
if (!dfn[*it]) tarjan(*it,-1,1);
//for (int i = h[*it]; i != -1; i=E[i].next) cout << E[i].from << " " << E[i].to << endl;
}
num++;
cout << "Network #" << num << endl;
if (!res.size()) cout << " No SPF nodes" << endl;
else {
set<pair<int,int> >::iterator it = res.begin();
for (; it!=res.end(); it++) cout << " SPF node " << (*it).first << " leaves " << (*it).second << " subnets" << endl;
}
cout << endl;
cin >> a;
}
}
tip:
1.链式前向星的结构,若使用vector来存储边,则h的初始值应该设置为-1,而不是0。
2.该题要求输出的节点编号是成顺序的,因此需要使用Set或者sort一下。