POJ 3177 Redundant Paths(shrink nodes and bridges)
Description
N个点M条无向边,原图是连通的,现要添加一些边,使得每两点之间的路至少有两条。求最少连接几条边。
Analzye
先进行环缩点,缩点完后是一棵树。
连接任意两个节点,皆可成环。
然而连接叶子节点比连接非叶子节点更优(需添加的边较少)。
而且要连接的这两个叶子节点,其到LCA的距离越大越优。
因为:连接树(缩点后的图)中某两个距离LCA最大的叶子节点,形成的环再次缩点会缩到根节点里,这样就减少了两个点。若非如此,每次只减少一个点。
答案:假设有ans个叶子节点,每添一条边减少两个点,需要添加
ans / 2
条边。若ans为奇数,需要(ans + 1) / 2
条边。两者可合并成后者。如何求缩点后叶子节点的个数ans?
叶子节点特征:度数为1
方案:求桥,原图的桥是缩点后的树上的边。遍历原图每个点,对每个点遍历出边,若此出边为桥,则它所连接的两点所在的连通分量(缩点后的点)度数++。最后遍历每个缩点后的点,度数为1的是叶子节点。
Code
#include <stack>
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 5005;
const int M = 50005;
int n, m;
int cnt, head[N];
struct edge
{
int v, pre;
bool flag;
} e[M];
void add(int u, int v)
{
e[cnt].v = v;
e[cnt].pre = head[u];
head[u] = cnt++;
}
stack<int> st;
bool inst[N];
int low[N], times[N], t;
int ltt[N], num, bridges;
void Tarjan(int x, int pre)
{
st.push(x);
inst[x] = 1;
low[x] = times[x] = ++t;
for(int i = head[x]; ~i; i = e[i].pre)
{
int to = e[i].v;
if(to == pre)
continue;
if(!times[to])
{
Tarjan(to, x);
low[x] = min(low[x], low[to]);
if(low[to] > times[x])
{
bridges++;
e[i].flag = e[i ^ 1].flag = 1;
}
}
else if(inst[to])
low[x] = min(low[x], times[to]);
}
if(low[x] == times[x])
{
num++;
while(!st.empty())
{
int tem = st.top();
st.pop();
ltt[tem] = num;
inst[tem] = 0;
if(tem == x)
break;
}
}
}
int degrees[N];
void init()
{
while(!st.empty())
st.pop();
cnt = t = bridges = num = 0;
memset(ltt, 0, sizeof(ltt));
memset(low, 0, sizeof(low));
memset(times, 0, sizeof(times));
memset(inst, 0, sizeof(inst));
memset(head, -1, sizeof(head));
memset(degrees, 0, sizeof(degrees));
}
int main()
{
int u, v;
while(~scanf("%d %d", &n, &m))
{
init();
for(int i = 0; i < m; ++i)
{
scanf("%d %d", &u, &v);
add(u, v);
add(v, u);
}
Tarjan(1, -1);///Original graph is connected.
for(int i = 1; i <= n; ++i)
for(int j = head[i]; ~j; j = e[j].pre)
if(e[j].flag)
degrees[ ltt[i] ]++;
int ans = 0;
for(int i = 1; i <= num; ++i)
if(degrees[i] == 1)
ans++;
cout << (ans + 1) / 2 << '\n';
}
return 0;
}
Something
有向图的环缩点可以是
Tarjan(int x)
,利用vis[]
协助。无向图的环缩点需要是
Tarjan(int x, int pre)
,避免遍历到反向边。关于避免访问到反向边而又不错过重边:
正常情况下:
Tarjan(int x, int pre_edge)
配合if(i == (pre_edge ^ 1))continue;
如果原图保证没有重边,可以偷懒:
Tarjan(int x, int pre_node)
配合if(to == pre_node)continue;