题意:定义$Tour \, Belt$为某张图上的一个满足以下条件的点集:①点集中至少有$2$个点②任意两点互相连通③图上两个端点都在这个点集中的边的权值的最小值严格大于图上只有一个端点在这个点集中的边的权值的最大值。现在给你一张$N$个点,$M$条边的图,请给出这张图上所有$Tour\,Belt$中包含的点数的和。$N \leq 5000 , M \leq \frac{N(N - 1)}{2}$
虽然这道题没有必要用$Kruskal$重构树来写,但是考%你赛的时候写$Kruskal$重构树的时候写挂了(LCA写错了),所以补上这个坑
因为我们需要某个连通块中所有边都大于其连向外面的边,所以考虑使用最大生成树的加边方式,每一次形成一个新的连通块的时候统计一次答案。可是我们不知道这个连通块中是否还有边没有被加进去,无法知道那些没有被加入的边是否会导致这个连通块的贡献变为$0$,所以实现有些麻烦(实际上根据这一点可以写出$O(N^2)$的并查集写法,然而咕咕咕咕咕)
我们自然地想到使用$Kruskal$重构树解决这个问题。在$Kruskal$重构树中,每个非叶子节点对应一个$Kruskal$过程中形成的连通块,而它的贡献就是这个子树中的叶子节点的个数。我们在非叶子节点中额外记录一个合并时的边权。在$Kruskal$重构树建好后,一遍$dfs$建好树上倍增,然后我们就可以考虑非树边对现有的连通块的影响了。
首先我们可以知道,在$Kruskal$重构树上,某条边的影响范围从两个端点的$LCA$开始,而它会使根到$LCA$路径上父亲对应边权大于等于当前边边权的点失去贡献,因为其父亲合并时的边权就是当前连通块中只连一个端点的边中最大的边权。可以知道这在$Kruskal$重构树上是一条链。我们就可以使用树上倍增找到满足这个条件的深度最低的点,通过树上差分对这一条链打上标记,最后遍历整棵树统计答案。
时间复杂度为$O(MlogN)$,理论上过不了实际上跑得飞快???
我的第一棵$Kruskal$重构树qaq
#include<bits/stdc++.h> #define MAXM 1000010 #define MAXN 5010 using namespace std; inline int read() { ; ; char c = getchar(); while(!isdigit(c)) { if(c == '-') f = ; c = getchar(); } while(isdigit(c)) { a = (a << ) + (a << ) + (c ^ '); c = getchar(); } return f ? -a : a; } struct Edge { int start , end , w; } Ed[MAXM]; ][] , ch[MAXN << ][] , size[MAXN << ] , fa[MAXN << ] , dep[MAXN << ] , tag[MAXN << ] , now[MAXN << ]; int N , M , cntNode , ans; bool vis[MAXM]; bool cmp(Edge a , Edge b) { return a.w > b.w; } int find(int a) { return fa[a] == a ? a : (fa[a] = find(fa[a])); } void dfs(int now , int fa) { dep[now] = dep[fa] + ; to[now][] = fa; ; i <= ; i++) to[now][i] = to[to[now][i - ]][i - ]; ]) { dfs(ch[now][] , now); dfs(ch[now][] , now); } } inline int jumpToLCA(int p , int q) { if(dep[p] < dep[q]) swap(p , q); ; i >= ; i--) << i) >= dep[q]) p = to[p][i]; if(p == q) return p; ; i >= ; i--) if(to[p][i] != to[q][i]) { p = to[p][i]; q = to[q][i]; } ]; } int jump(int n , int w) { ; i >= ; i--) if(now[to[n][i]] >= w) n = to[n][i]; return n; } int Dfs(int now) { ]) { ]) + Dfs(ch[now][]); ) ans += size[now]; tag[now] = ; return t; } ; } int main() { for(int T = read() ; T ; T--) { dep[] = ans = ; memset(vis , , sizeof(vis)); memset(ch , , sizeof(ch)); memset(now , , sizeof(now)); cntNode = N = read(); M = read(); ; i <= N ; i++) { fa[i] = i; size[i] = ; } ; i <= M ; i++) { Ed[i].start = read(); Ed[i].end = read(); Ed[i].w = read(); } sort(Ed + , Ed + M + , cmp); ; i <= M ; i++) { int p = find(Ed[i].start) , q = find(Ed[i].end); if(p != q) { fa[p] = fa[q] = fa[cntNode] = ++cntNode; ch[cntNode][] = p; ch[cntNode][] = q; size[cntNode] = size[p] + size[q]; now[cntNode] = Ed[i].w; if(now[p] == Ed[i].w){ tag[p]--; tag[cntNode]++; } if(now[q] == Ed[i].w){ tag[q]--; tag[cntNode]++; } vis[i] = ; } } dfs(cntNode , cntNode); ; i <= M ; i++) if(!vis[i]) { int t = jumpToLCA(Ed[i].end , Ed[i].start); int p = jump(t , Ed[i].w); tag[t]--; tag[p]++; } Dfs(cntNode); cout << ans << endl; } ; }
题外话:
论把
inline int jumpToLCA(int p , int q) { if(dep[p] < dep[q]) swap(p , q); ; i >= ; i--) << i) >= dep[q]) p = to[p][i]; if(p == q) return p; ; i >= ; i--) if(to[p][i] != to[q][i]) { p = to[p][i]; q = to[q][i]; } ]; }
写成
inline int jumpToLCA(int p , int q) { if(dep[p] < dep[q]) swap(p , q); ; i >= ; i--) << i) >= q) p = to[p][i]; if(p == q) return p; ; i >= ; i--) if(to[p][i] != to[q][i]) { p = to[p][i]; q = to[q][i]; } ]; }
考%你赛还查不出来qwq