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;
}