@DATASOURCE
2015-09-01T22:19:59.000000Z
字数 12423
阅读 1566
图论
连通性
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) + 2
using 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) + 2
using 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) + 2
using 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) + 2
using 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;
}