CF603E Pastoral Oddities 題解

下蛋爷發表於2024-11-17

Description

給定一張 \(n\) 個點的無向圖,初始沒有邊。

依次加入 \(m\) 條帶權的邊,每次加入後詢問是否存在一個邊集,滿足每個點的度數均為奇數。

若存在,則還需要最小化邊集中的最大邊權。

\(n \le 10^5\)\(m \le 3 \times 10^5\)

Solution

考慮給定一個圖,怎麼判斷這個圖存在一個邊集滿足條件。

結論是這個圖的每個連通塊大小為偶數就合法,否則不合法。證明就考慮如果存在一個連通塊大小為奇數,則這個連通塊最終總度數一定為奇數,而顯然加入一條邊所有點的度數和奇偶性不變,仍為偶數,所以矛盾。

如果一個連通塊大小為偶數,就隨便拿出一個生成樹,然後從葉子向根考慮。如果一個點兒子連過來的邊有偶數個,這個點就連父親,否則不連。剩下的根節點也一定滿足條件。

所以如果固定邊集的可選範圍,只需要先對於邊權從小到大排序,在加邊的過程中維護奇連通塊的個數即可。

但是如果需要動態加邊,上面那個做法就沒用了,因為你無法確定某一個時刻的連通塊狀態。

考慮線段樹分治。

由於答案從後往前不降,所以第 \(i\) 條邊在最優邊集中存在的時間一定是一個以 \(i\) 為左端點區間。線段樹分治時先分治右子樹,再分治左子樹,到葉子時如果存在奇連通塊就加入新邊,直到不存在奇連通塊。由於在加邊的過程中新加入的邊的存在時間被確定,所以線上段樹上更新即可。

時間複雜度:\(O(m\log n\log m)\)

Code

#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 1e5 + 5, kMaxM = 3e5 + 5;

int n, m, p, cnt_odd;
int ans[kMaxM];
std::tuple<int, int, int, int> ed[kMaxM];
std::vector<std::pair<int, int>> vec[kMaxM * 4];

struct DSU {
  int fa[kMaxN], sz[kMaxN], rnk[kMaxN];
  std::vector<std::tuple<int, int, int>> vec;

  void init() {
    for (int i = 1; i <= n; ++i)
      fa[i] = i, sz[i] = 1, rnk[i] = 0;
    cnt_odd = n;
  }

  void back(int t) {
    for (; vec.size() > t; vec.pop_back()) {
      auto [fx, fy, det] = vec.back();
      cnt_odd -= (sz[fy] & 1);
      fa[fx] = fx, rnk[fy] -= det, sz[fy] -= sz[fx];
      cnt_odd += (sz[fx] & 1) + (sz[fy] & 1);
    }
  }

  int find(int x) { return x == fa[x] ? x : find(fa[x]); }
  void unionn(int x, int y) {
    int fx = find(x), fy = find(y);
    if (fx == fy) return;
    if (rnk[fx] > rnk[fy]) std::swap(fx, fy);
    int det = (rnk[fx] == rnk[fy]);
    cnt_odd -= (sz[fx] & 1) + (sz[fy] & 1);
    fa[fx] = fy, rnk[fy] += det, sz[fy] += sz[fx];
    vec.emplace_back(fx, fy, det);
    cnt_odd += (sz[fy] & 1);
  }
} dsu;

void update(int x, int l, int r, int ql, int qr, std::pair<int, int> ed) {
  if (l > qr || r < ql) return;
  else if (l >= ql && r <= qr) return void(vec[x].emplace_back(ed));
  int mid = (l + r) >> 1;
  update(x << 1, l, mid, ql, qr, ed), update(x << 1 | 1, mid + 1, r, ql, qr, ed);
}

void solve(int x, int l, int r) {
  int t = dsu.vec.size();
  for (auto [u, v] : vec[x]) dsu.unionn(u, v);
  if (l == r) {
    for (; p < m && cnt_odd;) {
      ++p;
      auto [w, u, v, id] = ed[p];
      if (id <= l) {
        dsu.unionn(u, v);
        update(1, 1, m, id, l - 1, {u, v});
      }
    }
    if (!cnt_odd) ans[l] = std::get<0>(ed[p]);
    else ans[l] = -1;
  } else {
    int mid = (l + r) >> 1;
    solve(x << 1 | 1, mid + 1, r);
    solve(x << 1, l, mid);
  }
  dsu.back(t);
}

void dickdreamer() {
  std::cin >> n >> m;
  for (int i = 1; i <= m; ++i) {
    int u, v, w;
    std::cin >> u >> v >> w;
    ed[i] = {w, u, v, i};
  }
  std::sort(ed + 1, ed + 1 + m);
  dsu.init();
  solve(1, 1, m);
  for (int i = 1; i <= m; ++i) std::cout << ans[i] << '\n';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}