CF1681F. Unique Occurrences (可撤销并查集, 分治)

https://codeforces.com/contest/1681/problem/F
在知乎上看到严格鸽的教程,从来没见过这个东西,学习了。
知乎严格鸽:
https://zhuanlan.zhihu.com/p/521111820
https://zhuanlan.zhihu.com/p/521344322
题意:给5e5节点的树,问所有路径的贡献和,一条路径的贡献指路径上上只出现一次的边权的个数。
思路:对于每种边权的贡献:对于边权w,在图上就是边权w的边所分割的两个连通块的sz乘积。
我们需要知道去掉每一种边权的边的贡献,如果从1-n枚举边权去做n次并查集时间复杂度是n方的。
但是有“可撤销并查集这种东西”,我们只需要把边权分治,dfs(l,r)l==r时就是删除权值l时的连通块情况。 比如w,和w+1都属于很小的一个dfs(l, r)做完w可以logn的递归到w+1。

#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false) ,cin.tie(0), cout.tie(0);
//#pragma GCC optimize(3,"Ofast","inline")
#define ll long long
#define PII pair<int, int>
//#define int long long
const int N = 5e5 + 5;
const int M = 1e6 + 5;
const int INF = 0x3f3f3f3f;
const ll LNF = 0x3f3f3f3f3f3f3f3f;
const int mod = 998244353;
const double PI = acos(-1.0);
int n; ll ans = 0;
vector<PII> mp[N];
struct DSU {
  int fa[N], sz[N];
  vector<pair<int&, int>> his_sz;
  vector<pair<int&, int>> his_fa;
  void init( int n ) {for ( int i = 1; i <= n; ++ i ) fa[i] = i, sz[i] = 1;}
  int find( int x ) {
    while( x != fa[x] ) x = fa[x];
    return x;
  }
  bool same(int u, int v) {
    return find(u) == find(v);
  }
  int size(int u) {
    return sz[find(u)];
  }
  //让sz小的x的爹变成y
  //修改了sz[y]和fa[x],记录着俩值
  void merge( int a, int b) {
    int x = find(a), y = find(b);
    if( x == y ) return;
    if(sz[x] > sz[y]) swap(x, y);
    his_sz.push_back({sz[y], sz[y]}); 
    sz[y] += sz[x];
    his_fa.push_back({fa[x], fa[x]});
    fa[x] = y;
  }
  int history() { //历史戳
    return his_fa.size();
  }
  void roll ( int h ) { //回滚到历史h处
    while( his_fa.size() > h ) {
      his_fa.back().first = his_fa.back().second;
      his_fa.pop_back();
      his_sz.back().first = his_sz.back().second;
      his_sz.pop_back();
    }
  }
}dsu;
void dfs(int l, int r) {
    if(l == r) { // 删去权值为l的边
        for (auto i : mp[l]) {
          int sb1 = dsu.size(i.first), sb2 = dsu.size(i.second);
            ans += 1ll * dsu.size(i.first) * dsu.size(i.second);
        }
        return;
    }
    int mid = l + r >> 1;
    int h = dsu.history(); //历史戳
    for ( int i = l; i <= mid; ++ i ) {
        for (auto [u, v] : mp[i]) {
            dsu.merge(u, v);
        }
    }
    dfs(mid + 1, r);
    dsu.roll(h);//回滚到当前层的历史戳
    for ( int i = mid + 1; i <= r; ++ i ) {
        for (auto [u, v] : mp[i]) {
            dsu.merge(u, v);
        }
    }
    dfs(l, mid);
    dsu.roll(h);
}
void solve() {
    cin >> n;
    dsu.init(n);
    for ( int i = 1; i <= n - 1; ++ i ) {
        int u, v, w; cin >> u >> v >> w;
        mp[w].push_back({u, v});
    }
    dfs(1, n);
    cout << ans << '\n';
}
int main() {
    IOS
    int t = 1; //cin >> t;
    while( t -- ) solve();
    return 0;
}
posted @ 2022-10-06 10:58  qingyanng  阅读(61)  评论(0编辑  收藏  举报