Kruskal重构树
首先我们需要会 Kruskal 求最小生成树。就是非常简单的那个求法,将边排序后并查集往里连边。
Kruskal 重构树有什么用呢?对于一张图,我们想知道从 s 出发,经过的边的边权不超过 x ,哪些点可达。朴素BFS可以得到 \(O(n)\) 的做法。可是当我们需要用这个点集搭配数据结构使用的时候,会发现复杂度完全不行qwq。
随便想一个例题,给出一个带边权无向图,点权初始为 0 。有两种操作,一种是查询某个点的点权,另一种是给定 u、x、z,要将从 u 出发,只经过边权不大于 x 的边所能到达的点的点权全都加上 z 。
似乎并不是很好做……至少菜菜的我不会。
Kruskal 重构树可以做到什么?它可以生成一颗树,对于原图中每一个点,都在树中有对应。还有另外一些点,表示一条边,点权为边权。我们想知道哪些点可以由 u 走不超过 k 的边到达,只需要从 u 开始向上跳,直到点权大于 k 。这时候该点子树内的节点全都可以到达。
那么这么好用的东西怎么建呢?
类似于最小生成树,将所有的边排序,然后按顺序处理。
对于一条边,检查是否在同一并查集内,若已经在一个并查集内了,则跳过。
否则的话我们新建一个节点,点权为边权。然后 fa[find(u)] = fa[find(v)] = NewNode;
即可。
例题:洛谷P4899
#include <cstdio>
#include <cstring>
#include <algorithm>
#define mid (l+r>>1)
using namespace std;
int read()
{
int a = 0,x = 1;char ch = getchar();
while(ch > '9' || ch < '0') {if(ch == '-') x = -1;ch = getchar();}
while(ch >= '0' && ch <= '9') {a = a*10 + ch-'0';ch = getchar();}
return a*x;
}
const int N=1e6+7;
int n,m,q,tot,fa[N],c1[N],c2[N],fa1[N][20],fa2[N][20],rt[N],ctot;
int pos1[N],pos2[N];
struct node{
int u,v,w;
}edge[N];
int head[N],go[N],nxt[N],cnt,siz1[N],siz2[N],dfn1[N],dfn2[N],pos[N];
void add(int u,int v)
{
// printf("u(%d) -> v(%d)\n",u,v);
go[++cnt] = v;
nxt[cnt] = head[u];
head[u] = cnt;
}
bool cmp1(node a,node b) {return a.w > b.w;}
bool cmp2(node a,node b) {return a.w < b.w;}
int find(int s) {return fa[s] == s ? s : fa[s] = find(fa[s]);}
void dfs(int u,int fa[N][20],int siz[N],int dfn[N])
{
siz[u] = 1;dfn[u] = ++cnt;//printf("%d\n",u);
for(int i = 1;i < 20;i ++) fa[u][i] = fa[fa[u][i-1]][i-1];
for(int e = head[u];e;e = nxt[e]) {
int v = go[e];
fa[v][0] = u;dfs(v,fa,siz,dfn);
siz[u] += siz[v];
}
}
int tre[N<<4],rs[N<<4],ls[N<<4];
void modify(int &r1,int r2,int l,int r,int p,int x)
{
r1 = ++ctot;ls[r1] = ls[r2],rs[r1] = rs[r2],tre[r1] = tre[r2];
if(l == r && l == p) {tre[r1] += x;return ;}
if(p <= mid) modify(ls[r1],ls[r2],l,mid,p,x);
else modify(rs[r1],rs[r2],mid+1,r,p,x);
tre[r1] = tre[ls[r1]] + tre[rs[r1]];
}
int query(int root,int l,int r,int ql,int qr)
{
if(l >= ql && r <= qr) return tre[root];
if(l > qr || r < ql) return 0;
return query(ls[root],l,mid,ql,qr) + query(rs[root],mid+1,r,ql,qr);
}
bool check(int l1,int r1,int l2,int r2)
{
return query(rt[r1],1,tot,l2,r2) - query(rt[l1-1],1,tot,l2,r2);
}
int main()
{
// freopen("random.in","r",stdin);
// freopen("sol.out","w",stdout);
n = read(),m = read(),q = read();
for(int i = 1;i <= m;i ++) {
int u = read()+1,v = read()+1;
edge[i] = (node){u,v,min(u,v)};
}
sort(edge+1,edge+1+m,cmp1);
for(int i = 1;i <= n;i ++) fa[i] = c1[i] = c2[i] = i;tot = n;
for(int i = 1;i <= m;i ++) {//the humen being can go
if(find(edge[i].u) == find(edge[i].v)) continue;
++tot;fa[tot] = tot;c1[tot] = edge[i].w;
add(tot,find(edge[i].u)),add(tot,find(edge[i].v));
fa[find(edge[i].u)] = tot,fa[find(edge[i].v)] = tot;
}
// for(int i = 1;i <= tot;i ++) printf("%d ",c1[i]);
// puts(""),puts("");
cnt = 0;dfs(tot,fa1,siz1,dfn1);
for(int i = 1;i <= m;i ++) edge[i].w = max(edge[i].u,edge[i].v);
for(int i = 1;i <= tot;i ++) head[i] = 0,fa[i] = i;
sort(edge+1,edge+1+m,cmp2);cnt = 0;tot = n;
for(int i = 1;i <= m;i ++) {
if(find(edge[i].u) == find(edge[i].v)) continue;
++tot;fa[tot] = tot;c2[tot] = edge[i].w;
add(tot,find(edge[i].u));add(tot,find(edge[i].v));
fa[find(edge[i].u)] = tot,fa[find(edge[i].v)] = tot;
}
cnt = 0;dfs(tot,fa2,siz2,dfn2);
// for(int i = 1;i <= tot;i ++) printf("%d ",c2[i]);
// puts(""),puts("");
for(int i = 1;i <= tot;i ++) pos1[dfn1[i]] = i,pos2[dfn2[i]] = i;
for(int i = 1;i <= tot;i ++) pos[i] = dfn2[pos1[i]];
for(int i = 1;i <= tot;i ++) {
rt[i] = rt[i-1];
if(pos1[i] <= n)modify(rt[i],rt[i-1],1,tot,pos[i],1);
}
// return 0;
for(int i = 1;i <= q;i ++) {
// return 0;
int s = read()+1,e = read()+1,l = read()+1,r = read()+1;
for(int j = 19;j >= 0;j --) if(c1[fa1[s][j]] && c1[fa1[s][j]] >= l) s = fa1[s][j];
for(int j = 19;j >= 0;j --) if(c2[fa2[e][j]] && c2[fa2[e][j]] <= r) e = fa2[e][j];
// printf("%d : %d\n%d %d %d %d\n",i,m,s,e,l,r);
printf("%d\n",check(dfn1[s],dfn1[s]+siz1[s]-1,dfn2[e],dfn2[e]+siz2[e]-1));
}
}
P.S.
也许会有向上跳的时候会不会跳到原先就在图内的点导致无点权的疑惑,但是会发现只有叶子节点是实点,其他的全都是虚点。