@DATASOURCE
2015-09-01T14:19:59.000000Z
字数 12423
阅读 1722
图论 连通性
void Tarjan(int u, int fa){int son = 0;vis[u] = 1;dfn[u] = low[u] = ++tdfn;for(int i = head[u]; i + 1; i = edge[i].nxt){int v = edge[i].v;if(vis[v] == 1 && v != fa) low[u] = min(low[u], dfn[v]);if(vis[v]) continue;Tarjan(v, u);son++;low[u] = min(low[u], low[v]);if((u == root && son > 1) || (u != root && dfn[u] <= low[v])) cut[u] = 1;}vis[u] = 2;}
调用时root = 1, Tarjan(1, -1).
// 当 cut[i] > 0 时有割点, 并且删除改点后, 图被分成 cut[i] + 1 个连通分量。void Tarjan(int u){vis[u] = 1;int son = 0;dfn[u] = low[u] = ++tdfn;for(int i = head[u]; i + 1; i = edge[i].nxt){int v = edge[i].v;if(vis[v]) low[u] = min(low[u], dfn[v]);else{Tarjan(v);low[u] = min(low[u], low[v]);if(dfn[u] <= low[v]) cut[u]++, son++;}}if(u == rt) cut[u] = son - 1 < 0 ? 0 : son - 1;}
调用时rt = 1, Tarjan(1).
另一种写法
// 当 cut[i] > 0 时有割点, 并且删除改点后, 图被分成 cut[i] + 1 个连通分量。void Tarjan(int u, int fa){vis[u] = 1;int son = 0;dfn[u] = low[u] = tdfn++;for(int i = head[u]; i + 1; i = edge[i].nxt){int v = edge[i].v;if(vis[v] == 1 && v != fa) low[u] = min(low[u], dfn[v]);if(vis[v]) continue;Tarjan(v, u);son++;low[u] = min(low[u], low[v]);if((u == rt && son > 1) || (u != rt && dfn[u] <= low[v])) cut[u]++;}vis[u] = 2;}
调用时rt = 1, Tarjan(1, -1);.
题意:一个无向图,现在要去掉其中一个点,要求去掉这个点之后,总连通分支数最大。
/*=============================================================================# Author: He Shuwei# Last modified: 2015-08-03 20:37# Filename: POJ_2117_割点.cpp# Description:=============================================================================*/#include <map>#include <cmath>#include <queue>#include <stack>#include <vector>#include <cstdio>#include <string>#include <cstdlib>#include <cstring>#include <iostream>#include <algorithm>#define lson l, mid, ls#define rson mid, r, rs#define ls (rt << 1) + 1#define rs (rt << 1) + 2using namespace std;const int MAXN = 10100;struct Edge{int v, nxt;};int root;int dfn[MAXN];int low[MAXN];int cut[MAXN];int vis[MAXN];int head[MAXN];Edge edge[3 * MAXN];int ecnt, n, m, tdfn;void init(){ecnt = 0;memset(vis, 0, sizeof(vis));memset(dfn, 0, sizeof(dfn));memset(cut, 0, sizeof(cut));memset(low, 0, sizeof(low));memset(edge, 0, sizeof(edge));memset(head, -1, sizeof(head));}void addEdge(int u, int v){edge[ecnt].v = v;edge[ecnt].nxt = head[u];head[u] = ecnt++;}void Tarjan(int u, int fa){int son = 0;vis[u] = 1;dfn[u] = low[u] = ++tdfn;for(int i = head[u]; i + 1; i = edge[i].nxt){int v = edge[i].v;if(vis[v] == 1 && v != fa) low[u] = min(low[u], dfn[v]);if(vis[v]) continue;Tarjan(v, u);son++;low[u] = min(low[u], low[v]);if((u == root && son > 1) || (u != root && dfn[u] <= low[v])) cut[u]++;}vis[u] = 2;}int main(){while(~scanf("%d%d", &n, &m)){if(!(n + m)) break;if(m == 0){printf("%d\n", n - 1);continue;}init();int u, v;for(int i = 0; i < m; i++){scanf("%d%d", &u, &v);addEdge(u, v);addEdge(v, u);}int ans = 0;int res = 0;for(int i = 0; i < n; i++){if(!dfn[i]){ans++;tdfn = 0;root = i;Tarjan(i, -1);}res = max(res, cut[i]);}printf("%d\n", ans + res);}return 0;}
// edge[i].id == -1 时, 这条边是重边, 故排除// 割边的编号保存在 res 数组中。void addEdge(int u, int v, int id){for(int i = head[u]; i + 1; i = edge[i].nxt){if(edge[i].v == v){edge[i].id = -1;return;}}edge[ecnt].v = v;edge[ecnt].id = id;edge[ecnt].nxt = head[u];head[u] = ecnt++;u = u ^ v, v = u ^ v, u = u ^ v;edge[ecnt].v = v;edge[ecnt].id = id;edge[ecnt].nxt = head[u];head[u] = ecnt++;}void Tarjan(int u, int fa){vis[u] = 1;dfn[u] = low[u] = ++tdfn;for(int i = head[u]; i + 1; i = edge[i].nxt){int v = edge[i].v;if(vis[v] == 1 && v != fa) low[u] = min(low[u], dfn[v]);if(vis[v]) continue;Tarjan(v, u);low[u] = min(low[u], low[v]);if(dfn[u] < low[v] && edge[i].id != -1) res[cnt++] = edge[i].id;}vis[u] = 2;}
void Tarjan(int u, int fa){sta.push(u);instack[u] = true;low[u] = dfn[u] = ++tdfn;for(int i = head[u]; i + 1; i = edge[i].nxt){int v = edge[i].v;if(v == fa) continue;if(!dfn[v]){Tarjan(v, u);low[u] = min(low[u], low[v]);}else if(instack[v]) low[u] = min(low[u], low[v]);}if(dfn[u] == low[u]){scc++;int top;do{top = sta.top();sta.pop();instack[top] = false;belong[top] = scc;}while(top != u && !sta.empty());}}
调用时
for(int i = 1; i <= n; i++)if(!dfn[i]) Tarjan(i, -1);
题意:有n个牧场,Bessie 要从一个牧场到另一个牧场,要求至少要有2条独立的路可以走。现已有m条路,求至少要新建多少条路,使得任何两个牧场之间至少有两条独立的路。两条独立的路是指:没有公共边的路,但可以经过同一个中间顶点。
分析:在同一个边双连通分量中,任意两点都有至少两条独立路可达,所以同一个边双连通分量里的所有点可以看做同一个点。
缩点后,新图是一棵树,树的边就是原无向图的桥。
现在问题转化为:在树中至少添加多少条边能使图变为双连通图。
结论:添加边数=(树中度为1的节点数+1)/2
具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。
其实求边双连通分量和求强连通分量差不多,每次访问点的时候将其入栈,当low[u]==dfn[u]时就说明找到了一个连通的块,则栈内的所有点都属于同一个边双连通分量,因为无向图要见反向边,所以在求边双连通分量的时候,遇到反向边跳过就行了
/*=============================================================================# Author: He Shuwei# Last modified: 2015-08-07 14:37# Filename: POJ_3177_3352_双连通图+缩点.cpp# Description:=============================================================================*/#include <map>#include <cmath>#include <queue>#include <stack>#include <vector>#include <cstdio>#include <string>#include <cstdlib>#include <cstring>#include <iostream>#include <algorithm>#define lson l, mid, ls#define rson mid, r, rs#define ls (rt << 1) + 1#define rs (rt << 1) + 2using namespace std;const int MAXN = 5050;const int MAXE = 20020;struct Edge{int v, nxt;};Edge edge[MAXE];stack <int> sta;bool instack[MAXN];int ecnt, n, m, scc, tdfn;int degree[MAXN], belong[MAXN];int head[MAXN], dfn[MAXN], low[MAXN];void init(){ecnt = scc = tdfn = 0;while(!sta.empty()) sta.pop();memset(dfn, 0, sizeof(dfn));memset(low, 0, sizeof(low));memset(edge, 0, sizeof(edge));memset(head, -1, sizeof(head));memset(degree, 0, sizeof(degree));memset(instack, 0, sizeof(instack));}void addEdge(int u, int v){edge[ecnt].v = v;edge[ecnt].nxt = head[u];head[u] = ecnt++;}void Tarjan(int u, int fa){sta.push(u);instack[u] = true;low[u] = dfn[u] = ++tdfn;for(int i = head[u]; i + 1; i = edge[i].nxt){int v = edge[i].v;if(v == fa) continue;if(!dfn[v]){Tarjan(v, u);low[u] = min(low[u], low[v]);}else if(instack[v]) low[u] = min(low[u], low[v]);}if(dfn[u] == low[u]){scc++;int top;do{top = sta.top();sta.pop();instack[top] = false;belong[top] = scc;}while(top != u && !sta.empty());}}int solve(){for(int i = 1; i <= n; i ++)if(!dfn[i]) Tarjan(1, -1);for(int i = 1; i <= n; i++){for(int j = head[i]; j + 1; j = edge[j].nxt){int v = edge[j].v;if(belong[i] != belong[v]) degree[belong[i]]++;}}int res = 0;for(int i = 1; i <= n; i++)if(degree[i] == 1) res++;int ans = (res + 1) / 2;return ans;}int main(){while(scanf("%d%d", &n, &m) != EOF){init();int u, v;for(int i = 0; i < m; i++){scanf("%d%d", &u, &v);addEdge(u, v);addEdge(v, u);}int ans = solve();printf("%d\n", ans);}return 0;}
void dfs(int u){vis[u] = true;for(int i = head[u]; i + 1; i = edge[i].nxt)if(!vis[edge[i].v]) dfs(edge[i].v);vs[vscnt++] = u;}void rdfs(int u, int k){vis[u] = true;cmp[u] = k;for(int i = rhead[u]; i + 1; i = redge[i].nxt)if(!vis[redge[i].v]) rdfs(redge[i].v, k);}int scc(){memset(vis, 0, sizeof(vis));vscnt = 0;for(int i = 1; i <= n; i++)if(!vis[i]) dfs(i);int scc_cnt = 0;memset(vis, 0, sizeof(vis));for(int i = vscnt - 1; i >= 0; i--)if(!vis[vs[i]]) rdfs(vs[i], scc_cnt++);return scc_cnt;}
void Tarjan(int u){sta.push(u);instack[u] = true;dfn[u] = low[u] = ++tdfn;for(int i = head[u]; i + 1; i = edge[i].nxt){int v = edge[i].v;if(dfn[v] == 0){Tarjan(v);low[u] = min(low[u], low[v]);}else if(instack[v]) low[u] = min(low[u], low[v]);}if(low[u] == dfn[u]){int top;scc_cnt++;do{top = sta.top();sta.pop();instack[top] = false;belong[top] = scc_cnt;}while(u != top && !sta.empty());}}
调用方法
for(int i = 1; i <= n; i++)if(!dfn[i]) Tarjan(i);
题意:最多加多少条边,使得原图不是强连通图
正向考虑有困难,不妨反向思考,既最少去掉几条边使得原图不是强连通。
总边数sum=n*(n-1)时肯定是强连通,已经给了m条边,sum-=m
这时把已经强连通的部分进行缩点,对于缩好的点我们把他们分成两部分,保证其中一部分到另一部分没有边(这两部分不强连通),再把sum减去两部分能构成所有的边数,取最大值即为答案
具体做时枚举每个小强连通块,找到num[i]*(n-num[i])最小的情况(num[i]为小强连通块点数),其中必须出度或入度为0的连通块才可以被选择
/*=============================================================================# Author: He Shuwei# Last modified: 2015-08-08 15:47# Filename: HDU_4635_加最多边使不强连通.cpp# Description:=============================================================================*/#include <map>#include <cmath>#include <queue>#include <stack>#include <vector>#include <cstdio>#include <string>#include <cstdlib>#include <cstring>#include <iostream>#include <algorithm>#define lson l, mid, ls#define rson mid, r, rs#define ls (rt << 1) + 1#define rs (rt << 1) + 2using namespace std;const int MAXN = 100020;const int MAXE = 200010;struct Edge{int v, nxt;};bool vis[MAXN];Edge edge[MAXE], redge[MAXE];int n, m, ecnt, recnt, vs_cnt, scc_cnt;int head[MAXN], rhead[MAXN], vs[MAXN], belong[MAXN], In[MAXN], Out[MAXN], num[MAXN];void init(){ecnt = recnt = vs_cnt = scc_cnt = 0;memset(In, 0, sizeof(In));memset(Out, 0, sizeof(Out));memset(num, 0, sizeof(num));memset(edge, 0, sizeof(edge));memset(head, -1, sizeof(head));memset(redge, 0, sizeof(redge));memset(rhead, -1, sizeof(rhead));}void addEdge(int *head, Edge *edge, int u, int v, int &cnt){edge[cnt].v = v;edge[cnt].nxt = head[u];head[u] = cnt++;}void dfs(int u){vis[u] = true;for(int i = head[u]; i + 1; i = edge[i].nxt){if(!vis[edge[i].v]) dfs(edge[i].v);}vs[vs_cnt++] = u;}void rdfs(int u, int k){vis[u] = true;belong[u] = k;for(int i = rhead[u]; i + 1; i = redge[i].nxt){if(!vis[redge[i].v]) rdfs(redge[i].v, k);}}void scc(){memset(vis, 0, sizeof(vis));for(int i = 1; i <= n; i++)if(!vis[i]) dfs(i);memset(vis, 0, sizeof(vis));for(int i = vs_cnt - 1; i >= 0; i--)if(!vis[vs[i]]) rdfs(vs[i], scc_cnt++);}void calDegree(){for(int u = 1; u <= n; u++){for(int i = head[u]; i + 1; i = edge[i].nxt){int v = edge[i].v;if(belong[u] != belong[v]) In[belong[v]]++, Out[belong[u]]++;}}}long long solve(){scc();calDegree();for(int i = 1; i <= n; i++)num[belong[i]]++;long long sss = (long long)n * (n - 1) - m;long long ans = 0;for(int i = 0; i < scc_cnt; i++)if(In[i] == 0 || Out[i] == 0)ans = max(ans, sss - (long long)num[i] * (n - num[i]));return scc_cnt == 1 ? -1 : ans;}int main(){int t;scanf("%d", &t);for(int Case = 1; Case <= t; Case++){init();scanf("%d%d", &n, &m);int u, v;for(int i = 0; i < m; i++){scanf("%d%d", &u, &v);addEdge(head, edge, u, v, ecnt);addEdge(rhead, redge, v, u, recnt);}long long ans = solve();printf("Case %d: %lld\n", Case, ans);}return 0;}
题目大意:
给出一个n*m的格子地图,每一格上面是0~9,“*”或“#”。如果格子上是数字代表这个格子上有当前数量的矿石。如果是“*” 代表着当前格子是一个传送阵可以传送到指定的地方。如果是“#”代表当前格子不可达。
现在有一个矿车在坐标(0,0),也就是左上角。他只能向右和下行驶。当遇到传送阵时可以被传送到指定的位置。当他遇到数字时就可以得到那些数量的矿石,那个地方的矿石数量就变为“0”。问矿车最多可以采多少矿。
解题思路:
1、我们先要根据格子地图建图。(注1)
2、因为建立出的图可能会有环,我们要用Tarjan算法将环缩成点。(注2)
3、我们将缩点处理后的图重新建图。(注3)
4、对图求最长路。
/*=============================================================================# Author: He Shuwei# Last modified: 2015-08-12 10:35# Filename: POJ_3592_缩点+SPFA.cpp# Description:=============================================================================*/#include <map>#include <cmath>#include <queue>#include <stack>#include <vector>#include <cstdio>#include <string>#include <cstdlib>#include <cstring>#include <iostream>#include <algorithm>#define lson l, mid, ls#define rson mid, r, rs#define ls (rt << 1) + 1#define rs (rt << 1) + 2using namespace std;const int MAXN = 2000;const int MAXE = 5000;struct Edge{int v, nxt;};char mp[MAXN][MAXN];Edge edge[MAXE];stack <int> sta;bool instack[MAXN];int n, m, scc_cnt, ecnt, tdfn;int head[MAXN], val[MAXN], dfn[MAXN], low[MAXN], belong[MAXN];int scnt;bool vis[MAXN];Edge sedge[MAXE];int shead[MAXN], sccval[MAXN], dis[MAXN];void init(){scc_cnt = ecnt = tdfn = scnt = 0;memset(vis, 0, sizeof(vis));memset(dis, 0, sizeof(dis));memset(val, 0, sizeof(val));memset(dfn, 0, sizeof(dfn));memset(low, 0, sizeof(low));while(!sta.empty()) sta.pop();memset(edge, 0, sizeof(edge));memset(head, -1, sizeof(head));memset(sedge, 0, sizeof(sedge));memset(shead, -1, sizeof(shead));memset(sccval, 0, sizeof(sccval));memset(belong, 0, sizeof(belong));memset(instack, 0, sizeof(instack));}void addEdge(int *head, Edge *edge, int u, int v, int &cnt){edge[cnt].v = v;edge[cnt].nxt = head[u];head[u] = cnt++;}void Input(){scanf("%d%d", &n, &m);for(int i = 0; i < n; i++)scanf("%s", mp[i]);int a, b;for(int i = 0; i < n; i++)for(int j = 0; j < m; j++){if(mp[i][j] == '#') continue;if(mp[i][j] >= '0' && mp[i][j] <= '9') val[m * i + j] = mp[i][j] - '0';else if(mp[i][j] == '*'){scanf("%d%d", &a, &b);addEdge(head, edge, m * i + j, a * m + b, ecnt);}if(i + 1 < n && mp[i + 1][j] != '#')addEdge(head, edge, m * i + j, m * (i + 1) + j, ecnt);if(j + 1 < m && mp[i][j + 1] != '#')addEdge(head, edge, m * i + j, m * i + j + 1, ecnt);}//for(int i = 0; i < n * m; i++)//cerr <<"i = "<< i <<", val[i] = "<< val[i] << endl;}void Tarjan(int u){sta.push(u);instack[u] = true;dfn[u] = low[u] = ++tdfn;for(int i = head[u]; i + 1; i = edge[i].nxt){int v = edge[i].v;if(!dfn[v]){Tarjan(v);low[u] = min(low[u], low[v]);}else if(instack[v]) low[u] = min(low[u], low[v]);}if(low[u] == dfn[u]){++scc_cnt;int top;do{top = sta.top();sta.pop();instack[top] = false;belong[top] = scc_cnt;sccval[scc_cnt] += val[top];//cerr <<"scc_cnt = "<< scc_cnt <<", top = "<< top <<", val[top] = "<< val[top] <<", sccval[scc_cnt] = "<< sccval[scc_cnt] << endl;}while(top != u && !sta.empty());}}void SPFA(int src){queue <int> que;que.push(src);vis[src] = true;dis[src] = sccval[src];while(!que.empty()){int u = que.front();que.pop();vis[u] = false;for(int i = shead[u]; i + 1; i = sedge[i].nxt){int v = sedge[i].v;dis[v] = max(dis[v], dis[u] + sccval[v]);if(!vis[v]) que.push(v), vis[v] = true;}}}void solve(){for(int i = 0; i < n * m; i++)if(!dfn[i]) Tarjan(i);//cerr <<"scc_cnt = "<< scc_cnt << endl;//for(int i = 0; i < n * m; i++)//cerr <<"i = "<< i <<", belong[i] = "<< belong[i] <<", sccval[belong[i]] = "<< sccval[belong[i]] << endl;for(int u = 0; u < n * m; u++)for(int i = head[u]; i + 1; i = edge[i].nxt){int v = edge[i].v;if(belong[u] != belong[v]) addEdge(shead, sedge, belong[u], belong[v], scnt);}SPFA(belong[0]);int ans = 0;for(int i = 1; i <= scc_cnt; i++)ans = max(ans, dis[i]);printf("%d\n", ans);}int main(){int t;scanf("%d", &t);while(t--){init();Input();solve();}return 0;}