线段树分治

前置知识:可撤销化并查集

注意:可撤销化并查集的作用和删边不一样,其只能撤销最近的一次操作。

既然只需撤销,那么只需在在合并并查集时用个 vector 记录下合并的哪两个点,撤销时就直接还原就行了。

这里要强调一下,可撤销化并查集不能路径压缩,只能启发式合并。

代码

int f[MAXN], sz[MAXN];
vector<pii> edge;

int getfa(int u) {
  return (!f[u] ? u : getfa(f[u]));
}

bool Merge(int u, int v) {
  u = getfa(u), v = getfa(v);
  if(u == v) {
    edge.emplace_back(0, 0);
    return 0;
  }
  if(sz[u] > sz[v]) {
    swap(u, v);
  }
  f[u] = v, sz[v] += sz[u];
  edge.emplace_back(u, v);
  return 1;
}

bool Cancal() {
  auto [u, v] = edge.back();
  edge.pop_back();
  sz[v] -= sz[u], f[u] = 0;
  return u && v;
}

线段树分治

这里只讲以下这个问题,其他的可以用类似的方法解决:

给定一个 \(N\) 个结点的图,初始时图上没有边,接下来将会进行一系列加入/删除边的操作,请在每次询问时求出图中连通块数量。

我们可以去思考每一条边在哪些时刻存在,以下面这个为例:

在图中,绿色的区域表示这条边存在的时间段。然后,我们考虑用类似于线段树的方式进行区间修改。

我们对以下这个样例画一棵线段树:

+ 1 2
+ 3 4
+ 1 5
- 1 5
- 1 2
+ 1 3
+ 4 5
- 1 3

这里每个结点都有一个 vector 来记录它所对应的区间要加入的边,接着我们考虑在这个线段树上遍历。

image-20240902204420022

在遍历时,进入某个结点我们就把绿色的边加入,回溯时就把红色的边删除(撤销)。并在每个叶子节点处记录答案即可。

空间复杂度 \(O(N+Q)\),时间复杂度 \(O(Q\log Q\cdot\log N)\)

代码

#include<bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;

const int MAXN = 300005;

int f[MAXN], sz[MAXN], cnt;
vector<pii> edge;

int getfa(int u) {
  return (!f[u] ? u : getfa(f[u]));
}

bool Merge(int u, int v) {
  u = getfa(u), v = getfa(v);
  if(u == v) {
    edge.emplace_back(0, 0);
    return 0;
  }
  if(sz[u] > sz[v]) {
    swap(u, v);
  }
  f[u] = v, sz[v] += sz[u];
  edge.emplace_back(u, v);
  return 1;
}

bool Cancal() {
  auto [u, v] = edge.back();
  edge.pop_back();
  sz[v] -= sz[u], f[u] = 0;
  return u && v;
}

struct Segment_Tree {
  int l[MAXN << 2], r[MAXN << 2], ans[MAXN];
  vector<pii> e[MAXN << 2];
  void build(int u, int s, int t) {
    if(s > t) {
      return;
    }
    l[u] = s, r[u] = t;
    if(s == t) {
      return;
    }
    int mid = (s + t) >> 1;
    build(u << 1, s, mid), build((u << 1) | 1, mid + 1, t);
  }
  void update(int u, int s, int t, pii g) {
    if(l[u] >= s && r[u] <= t) {
      e[u].push_back(g);
      return;
    }
    if(s <= r[u << 1]) {
      update(u << 1, s, t, g);
    }
    if(t >= l[(u << 1) | 1]) {
      update((u << 1) | 1, s, t, g);
    }
  }
  void Getans(int u) {
    for(auto [u, v] : e[u]) {
      cnt -= Merge(u, v);
    }
    if(l[u] == r[u]) {
      ans[l[u]] = cnt;
      for(auto [u, v] : e[u]) {
        cnt += Cancal();
      }
      return;
    }
    Getans(u << 1), Getans((u << 1) | 1);
    for(auto [u, v] : e[u]) {
      cnt += Cancal();
    }
  }
}tr;

int n, q;
bool op[MAXN];
map<int, int> l[MAXN];
vector<pii> g;

void FileIO(const string &s) {
  freopen((s + ".in").c_str(), "r", stdin);
  freopen((s + ".out").c_str(), "w", stdout);
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  FileIO("connect");
  cin >> n >> q;
  cnt = n;
  tr.build(1, 1, q);
  for(int i = 1, u, v; i <= q; ++i) {
    char c;
    cin >> c;
    if(c == '+') {
      cin >> u >> v;
      l[u][v] = l[v][u] = i;
      g.emplace_back(u, v);
    }else if(c == '-') {
      cin >> u >> v;
      tr.update(1, l[u][v], i - 1, {u, v});
      l[u][v] = l[v][u] = 0;
    }else {
      op[i] = 1;
    }
  }
  for(auto [u, v] : g) {
    if(l[u][v]) {
      tr.update(1, l[u][v], q, {u, v});
    }
  }
  fill(sz + 1, sz + n + 1, 1);
  tr.Getans(1);
  for(int i = 1; i <= q; ++i) {
    if(op[i]) {
      cout << tr.ans[i] << "\n";
    }
  }
  return 0;
}

AT ABC308 H

题目描述

给定一个 \(N\) 个点 \(M\) 条边的无向带权图,求一个 \(Q\) 的边权之和的最小值。这里"一个 \(Q\)" 是指:

  • 恰好存在一条边,使得删去它之后剩下的部分为一个简单环。

思路

由于 \(N\) 只有 \(300\),所以考虑枚举 \(Q\) 上多的一条边和环相交的点 \(u\),然后枚举两个相邻的点作为环上另外两个点 \(v_1,v_2\)

此时环的另外一半就取从 \(v_1\)\(v_2\) 的最短路,但是如果最短路会经过点 \(u\) 就不合法,所以我们要求在图上删掉点 \(u\) 的最短路。而这个东西可以用线段树分治预处理。我们在线段树中插入的都是用作中转站的点。每次插入新的点就做一遍 Floyd。

但这时最后一条不在环上的边就不能用枚举求出了,所以我们可以先求出 \(u\) 出边的最小/次小/次次小的边,然后看哪条边没被用过就行了。

时空复杂度均为 \(O(N^3)\)

代码

#include<bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;

const int MAXN = 305, INF = int(1e9);

int n, d[MAXN][MAXN], dist[MAXN][MAXN][MAXN];

struct Segment_Tree {
  int l[MAXN << 2], r[MAXN << 2];
  vector<int> ve[MAXN << 2];
  void build(int u, int s, int t) {
    l[u] = s, r[u] = t;
    if(s == t) {
      return;
    }
    int mid = (s + t) >> 1;
    build(u << 1, s, mid), build((u << 1) | 1, mid + 1, t);
  }
  void update(int u, int s, int t, int x) {
    if(s > t) {
      return;
    }
    if(l[u] >= s && r[u] <= t) {
      ve[u].push_back(x);
      return;
    }
    if(s <= r[u << 1]) {
      update(u << 1, s, t, x);
    }
    if(t >= l[(u << 1) | 1]) {
      update((u << 1) | 1, s, t, x);
    }
  }
  void Floyd(int u) {
    vector<vector<int>> last(n + 1, vector<int>(n + 1));
    for(int i = 1; i <= n; ++i) {
      for(int j = 1; j <= n; ++j) {
        last[i][j] = d[i][j];
      }
    }
    for(int x : ve[u]) {
      for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= n; ++j) {
          d[i][j] = min(d[i][j], d[i][x] + d[x][j]);
        }
      }
    }
    if(l[u] == r[u]) {
      for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= n; ++j) {
          dist[l[u]][i][j] = d[i][j];
        }
      }
      for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= n; ++j) {
          d[i][j] = last[i][j];
        }
      }
      return;
    }
    Floyd(u << 1), Floyd((u << 1) | 1);
    for(int i = 1; i <= n; ++i) {
      for(int j = 1; j <= n; ++j) {
        d[i][j] = last[i][j];
      }
    }
  }
}tr;

int m, ans;
vector<pii> e[MAXN];

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> m;
  for(int i = 1; i <= n; ++i) {
    for(int j = 1; j <= n; ++j) {
      d[i][j] = (i != j) * INF;
    }
  }
  for(int i = 1, u, v, w; i <= m; ++i) {
    cin >> u >> v >> w;
    e[u].push_back({v, w});
    e[v].push_back({u, w});
    d[u][v] = d[v][u] = w;
  }
  tr.build(1, 1, n);
  for(int i = 1; i <= n; ++i) {
    tr.update(1, 1, i - 1, i);
    tr.update(1, i + 1, n, i);
  }
  tr.Floyd(1);
  ans = INF;
  for(int i = 1; i <= n; ++i) {
    int Min = INF, Min2 = INF, Min3 = INF, V = 0, V2 = 0, V3 = 0;
    for(auto [v, w] : e[i]) {
      if(w < Min) {
        Min3 = Min2, V3 = V2, Min2 = Min, V2 = V, Min = w, V = v;
      }else if(w < Min2) {
        Min3 = Min2, V3 = V2, Min2 = w, V2 = v;
      }else if(w < Min3) {
        Min3 = w, V3 = v;
      }
    }
    for(int j = 0; j < int(e[i].size()); ++j) {
      for(int k = j + 1; k < int(e[i].size()); ++k) {
        auto [v, w] = e[i][j];
        auto [v2, w2] = e[i][k];
        int x;
        if(v != V && v2 != V) {
          x = Min;
        }else if(v != V2 && v2 != V2) {
          x = Min2;
        }else {
          x = Min3;
        }
        ans = min(0ll + ans, 0ll + w + w2 + x + dist[i][v][v2]);
      }
    }
  }
  cout << (ans == INF ? -1 : ans);
  return 0;
}
posted @ 2024-09-02 20:48  Yaosicheng124  阅读(8)  评论(0编辑  收藏  举报