题意:
给一个无向图和它的一个生成树,要求找到一个最小割,使得有且只有一条生成树上的一条边属于割集。
解析:
因为生成树中只有一条边属于割集,那么割对生成树来说只是分成了两个子树,那么就考虑割生成树上割哪条边是最优的。
首先对生成树进行建树剖,对于每条非树边的两个端点u和v,对 u –> v 在生成树上的简单路径上的边权值加一,最后找到所有边权值最小的边,就是属于最小割的边。
所有对找到的这条边的权值做贡献的边,一定是跨越了以这条边分开的两个子树,即如果要分开这两个子树,这些边也要割掉,这些边就是要求的最小割集。这题卡常数,所以路径上边权加一的操作不能用线段树或者树状数组来更新,因为是操作完成后才对边权进行遍历,所以可以用差分前缀和来计算每条边被更新了几次。
my code
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <vector>
#define pb push_back
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 20005;
const int M = 200005;
namespace IO {
const static int maxn = 200 << 18;
static char buf[maxn], *pbuf = buf, *End;
void init() {
int c = fread(buf, 1, maxn, stdin);
End = buf + c;
}
int &readint() {
static int ans;
ans = 0;
while (pbuf != End && !isdigit(*pbuf)) pbuf ++;
while (pbuf != End && isdigit(*pbuf)) {
ans = ans * 10 + *pbuf - '0';
pbuf ++;
}
return ans;
}
}
struct Query {
int u, v;
} Q[M];
vector<int> G[N];
int fa[N], son[N], size[N], top[N], deep[N];
int p[N], fp[N], dfs_clock;
void dfs1(int u, int pre, int de) {
deep[u] = de, fa[u] = pre, size[u] = 1;
son[u] = 0;
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
if (v == pre) continue;
dfs1(v, u, de + 1);
size[u] += size[v];
if (size[v] > size[son[u]])
son[u] = v;
}
}
void dfs2(int u, int tp) {
top[u] = tp;
p[u] = ++dfs_clock;
fp[dfs_clock] = u;
if(son[u]) dfs2(son[u], tp);
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
if (v != fa[u] && v != son[u])
dfs2(v, v);
}
}
void init_chain(int u) {
dfs_clock = 0;
dfs1(u, 0, 1);
dfs2(u, u);
}
int n, m, q;
int sum[N];
inline void modify(int ql, int qr, int val) {
sum[ql] += val; sum[qr+1] -= val;
}
void build() {
for(int i = 1; i <= n; i++)
sum[i] += sum[i-1];
}
void update(int u, int v, int val) {
while (top[u] != top[v]) {
if (deep[top[u]] < deep[top[v]])
swap(u, v);
modify(p[top[u]], p[u], val);
u = fa[top[u]];
}
if (deep[u] < deep[v]) swap(u, v);
modify(p[v], p[u], val);
}
inline void addEdge(int u, int v) {
G[u].pb(v);
}
void init() {
q = 0;
memset(sum, 0, sizeof(sum));
for(int i = 1; i <= n; i++) G[i].clear();
}
int main() {
IO::init();
int u, v;
int T, cas = 1;
T = IO::readint();
while(T--) {
init();
n = IO::readint();
m = IO::readint();
for(int i = 1; i <= m; i++) {
u = IO::readint();
v = IO::readint();
if(i < n) {
addEdge(u, v);
addEdge(v, u);
}else
Q[q++] = (Query){u, v};
}
init_chain(1);
for(int i = 0; i < q; i++) {
u = Q[i].u, v = Q[i].v;
update(u, v, 1);
}
build();
int ans = INF;
for(int i = 2; i <= n; i++)
ans = min(ans, sum[i]+1);
printf("Case #%d: %d\n", cas++, ans);
}
return 0;
}