【补遗】 Let me sleep(NCD 2019, Gym - 102163B)

题意

在一张n个点,m条边的无向图中允许再加一条边,问增加后图中最少还有多少条割边。\((1≤N,M≤10^5)\)

分析

要注意这样一个情况,一般求割边在不是和网络流有关的情况下都是和双连通分量有关的。因为是割边,因此我们按照边双连通分量缩点,得到一个森林,此时森林里的每一个边都是割边。
然后接下来考虑增加边。在森林间的树之间增加边是很憨憨的一个行为,这只会增加桥;于是就是在树上加边。为了减少树上的割边,我们希望能够把树上最长路径的端点连接起来——也就是直径。
问题于是解决。

代码

#include <bits/stdc++.h>
#define fi first
#define se second
#define MP make_pair
#define MS(x, y) memset(x, y, sizeof(x))
#define rep(i, a, b) \
  for (repType i = static_cast<repType>(a); i <= static_cast<repType>(b); ++i)
#define REP(i, a, b) \
  for (repType i = static_cast<repType>(a); i < static_cast<repType>(b); ++i)
#define per(i, a, b) \
  for (repType i = static_cast<repType>(a); i >= static_cast<repType>(b); --i)
#define PER(i, a, b) \
  for (repType i = static_cast<repType>(a); i > static_cast<repType>(b); --i)
#define ALL(x) x.begin(), x.end()

using namespace std;
using repType = signed;
using ll = long long;
using ld = long double;
using pi = pair<int, int>;

const int MAXN = 100005;
struct Edge {
  int u, v, w;
  bool bri;
  Edge() = default;
  Edge(int u, int v, int w = 1) : u(u), v(v), w(w), bri(false) {}
};
vector<Edge> edges;
array<vector<int>, MAXN> G, bcc;
int pre[MAXN], iscut[MAXN], bccno[MAXN], dfs_clk, bcc_cnt, bri_cnt;

void add_edge(int u, int v) {
  edges.emplace_back(Edge(u, v));
  G[u].emplace_back(int(edges.size() - 1));
}

stack<Edge> S;
int tarjan(int u, int fa) {
  int lowu = pre[u] = ++dfs_clk;
  int child = 0;
  for (auto i : G[u]) {
    auto &e = edges[i];
    auto v = e.v;
    if (!pre[v]) {
      S.push(e);
      child++;
      int lowv = tarjan(v, u);
      lowu = min(lowu, lowv);

      if (lowv > pre[u]) {  // 桥
        bri_cnt++;
        edges[i].bri = edges[i ^ 1].bri = true;
      }
      if (lowv >= pre[u]) {  // 割点
        iscut[u] = true;
        bcc_cnt++;
        bcc[bcc_cnt].clear();  // 从1开始
        for (;;) {
          auto x = S.top();
          S.pop();
          if (bccno[x.u] != bcc_cnt) {
            bcc[bcc_cnt].emplace_back(x.u);
            bccno[x.u] = bcc_cnt;
          }
          if (bccno[x.v] != bcc_cnt) {
            bcc[bcc_cnt].emplace_back(x.v);
            bccno[x.v] = bcc_cnt;
          }
          if (x.u == u && x.v == v) break;
        }
      }
    } else if (pre[v] < pre[u] && v != fa) {
      S.push(e);
      lowu = min(lowu, pre[v]);
    }
  }
  if (fa < 0 && child == 1) iscut[u] = false;
  return lowu;
}

void find_bcc(int n) {
  MS(pre, 0);
  MS(iscut, 0);
  MS(bccno, 0);
  dfs_clk = bri_cnt = bcc_cnt = 0;
  REP(i, 0, n) {
    if (!pre[i]) tarjan(i, -1);
  }
}

int nG_cnt, nG_edge_cnt;
array<int, MAXN> nG_clr;
array<vector<int>, MAXN> nG;
array<unordered_set<int>, MAXN> nG_hash;
array<bool, MAXN> nG_vis;
array<int, MAXN> nG_dist;
void dfs1(int x) {  // 将图分成若干个边-双连通分量
  nG_clr[x] = nG_cnt;
  for (auto i : G[x]) {
    auto &e = edges[i];
    if (nG_clr[e.v] != -1 || e.bri) continue;
    dfs1(e.v);
  }
}

void build_nG() {
  for (auto e : edges) {
    int u = nG_clr[e.u], v = nG_clr[e.v];
    if (u != v && nG_hash[u].find(v) == nG_hash[u].end()) {
      nG_edge_cnt++;
      nG_hash[u].insert(v);
      nG_hash[v].insert(u);
      nG[u].emplace_back(v);
      nG[v].emplace_back(u);
    }
  }
}

pi dfs2(int x, int fa) {  // 返回从x出发的最深 <距离, 点>
  nG_vis[x] = true;
  if (fa != -1)
    nG_dist[x] = nG_dist[fa] + 1;
  else
    nG_dist[x] = 1;

  auto ret = MP(nG_dist[x], x);
  for (auto v : nG[x]) {
    if (v != fa) {
      auto pa = dfs2(v, x);
      if (pa > ret) {
        ret = pa;
      }
    }
  }
  return ret;
}

void init(int n) {
  edges.clear();
  rep(i, 0, n) {
    G[i].clear();
    nG[i].clear();
    nG_hash[i].clear();
  }
  nG_cnt = nG_edge_cnt = 0;
  fill(ALL(nG_clr), -1);
  fill(ALL(nG_vis), false);
}

signed main() {
  int T;
  cin >> T;
  while (T--) {
    int n, m;
    cin >> n >> m;
    init(n);
    rep(i, 1, m) {
      int u, v;
      cin >> u >> v;
      add_edge(u, v);
      add_edge(v, u);
    }
    find_bcc(n);
    rep(i, 1, n) {
      if (nG_clr[i] == -1) {
        nG_cnt++;
        dfs1(i);
      }
    }
    build_nG();
    int ans = 0;
    rep(i, 1, nG_cnt) {
      if (!nG_vis[i]) {
        auto pnt = dfs2(i, -1).second;
        ans = max(ans, dfs2(pnt, -1).first - 1);
      }
    }
    cout << nG_edge_cnt - ans << endl;
  }

  return 0;
}
上一篇:内网渗透测试:NTLM Relay攻击分析


下一篇:通过Linux上的Php计数共享Windows驱动器上的文件