算法笔记-Kruskal重构树

Kruskal重构树

在Kruskal算法求最小生成树时,按照边权排序,然后依次合并节点。

在构建Kruskal重构树时,按照边权排序,在合并节点 x , y x,y x,y时,断开 x , y x,y x,y之间的边,并新建节点 z z z z z z的点权为边 ( x , y ) (x,y) (x,y)的权值, z z z x , y x,y x,y分别连边,并用并查集维护连通性。

性质

  • 是二叉树
  • 如果按最小生成树顺序的话是大根堆
  • 原图上任意两点路径上边权的最大值是重构树上 l c a lca lca的点权

代码

void Kruskal(){
	sort(edg+1, edg+1+m, cmp);
	memset(e, 0, sizeof(e));
	memset(head, 0, sizeof(head));
	tot = 0;
	for(int i = 1; i <= n; i++)
		f[i] = i;
	nodetot = n;
	for(int i = 1; i <= m; i++){
		int fx = find(edg[i].x);
		int fy = find(edg[i].y);
		if(fx == fy)
			continue;
		val[++nodetot] = edg[i].v;
		f[nodetot] = f[fx] = f[fy] = nodetot;
		add(nodetot, fx); add(nodetot, fy);
	}
}


P4768 [NOI2018] 归程

题意:

给定一个连通图,边有两个权值,长度 l l l,高度 h h h。每次询问给定起点 s 0 s_0 s0,水位线 h 0 h_0 h0,高度小于等于 h 0 h_0 h0的边只能走路,不能开车。终点为 1 1 1号结点,询问从 s 0 s_0 s0到终点走路的最小长度。

解析:

将所有边按照高度排序,构建 k r u s k a l kruskal kruskal重构树,得到的是小根堆。对于一棵子树 u u u,如果水位线 h 0 h_0 h0小于子树根节点的权值,该子树中的叶子结点是可以互相到达的,即原图中对应的点可以开车直达。

对于当前询问 ( s 0 , h 0 ) (s_0,h_0) (s0,h0),找到根节点 u u u,满足 w [ u ] > h 0 w[u] > h_0 w[u]>h0 w [ f a [ u ] ] ≤ h 0 w[fa[u]] \le h_0 w[fa[u]]h0,则 s 0 s_0 s0可以开车到达的结点为子树 u u u的叶子结点。

可以利用 D i j s k r a Dijskra Dijskra求出所有点到 1 1 1号结点的最短路。按照高度排序,构建 k r u s k a l kruskal kruskal重构树,然后 d f s dfs dfs求出子树 u u u中到 1 1 1号结点路径的最小值 d [ u ] d[u] d[u]

对于每次询问 ( s 0 , h 0 ) (s_0,h_0) (s0,h0),利用倍增找到结点 u u u,答案为 d [ u ] d[u] d[u]

代码:

#include
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pll;
#define mkp(a, b) make_pair(a, b)
#define fi first
#define se second
const int maxn = 2e5+10;
const int maxm = 4e5+10;
const int INF = 0x3f3f3f3f;
struct edge{
	int to, nxt;
	ll v;//长度
}e[maxm<<1];
int head[maxn<<1], tot;
void add(int a, int b, ll c = 0){
	e[++tot].nxt = head[a];
	e[tot].to = b;
	e[tot].v = c;
	head[a] = tot;
}
struct node{
	int x, y, v;
	bool operator < (const node &b) const{
		return v > b.v;
	}
}edg[maxm];
int f[maxn<<1];
int find(int x){
	return x == f[x] ? x : f[x] = find(f[x]);
}
int nodetot;
int val[maxn<<1];
int n, m;
void Kruskal(){
	memset(e, 0, sizeof(e));
	memset(head, 0, sizeof(head));
	tot = 0;
	for(int i = 1; i <= n; i++)
		f[i] = i;
	nodetot = n;
	for(int i = 1; i <= m; i++){
		int fx = find(edg[i].x);
		int fy = find(edg[i].y);
		if(fx == fy)
			continue;
		val[++nodetot] = edg[i].v;
		f[nodetot] = f[fx] = f[fy] = nodetot;
		add(nodetot, fx); add(nodetot, fy);
	}
}
int vis[maxn];
ll dis[maxn<<1];
priority_queue<pll> q;
void Dijkstra(int s = 1){
	memset(vis, 0, sizeof(vis));
	memset(dis, INF, sizeof(dis));
	dis[1] = 0;
	q.push(mkp(0, 1));
	while(q.size()){
		int u = q.top().se; q.pop();
		if(vis[u])
			continue;
		vis[u] = 1;
		for(int i = head[u]; i; i = e[i].nxt){
			int v = e[i].to;
			if(dis[v] > dis[u]+e[i].v){
				dis[v] = dis[u]+e[i].v;
				q.push(mkp(-dis[v], v));
			}
		}
	}
}
int p[maxn<<1][20];
ll d[maxn<<1];
void dfs(int u, int fath){
	p[u][0] = fath;
	d[u] = dis[u];
	for(int i = 1; i <= 19; i++)
		p[u][i] = p[p[u][i-1]][i-1];
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		dfs(v, u);
		d[u] = min(d[u], d[v]);
	}
}
int find_node(int u, int x){
	int v = u;
	for(int i = 19; i >= 0; i--)
		if(val[p[v][i]] > x)
			v = p[v][i];
	return v;
}
int Q, K, S;
void init(){
	memset(head, 0, sizeof(head));
	tot = 0;
	memset(e, 0, sizeof(e));
	memset(p, 0, sizeof(p));
	memset(val, 0, sizeof(val));
	while(q.size())
		q.pop();
}
void solve(){
	cin >> n >> m;
	init();
	for(int i = 1; i <= m; i++){
		int x, y, v, h;
		cin >> x >> y >> v >> h;
		edg[i].x = x; edg[i].y = y; edg[i].v = h;
		add(x, y, v); add(y, x, v);
	}
	Dijkstra();
	sort(edg+1, edg+1+m);
	Kruskal();
	d[0] = -1;
	dfs(nodetot, 0);
	cin >> Q >> K >> S;
	ll last = 0;
	while(Q--){
		int v0, p0, v, p;
		cin >> v0 >> p0;
		v = (v0+K*last-1)%n+1;
		p = (p0+K*last)%(S+1);
		int x = find_node(v, p);
		last = d[x];
		//cout << "ans = ";
		cout << last << endl;
	}
}
int main(){
	int T;
	cin >> T;
	while(T--)
		solve();
	return 0;
}


P4197 Peaks

题意:

给定无向图,点有点权 h i h_i hi,边有边权 x i x_i xi。每次询问 ( v , x , k ) (v,x,k) (v,x,k),从 v v v出发,只经过边权小于 x x x的边,能够到达的点中点权第 k k k大的点权。

解析:

只经过边权小于 x x x的边可以想到 K r u s k a l Kruskal Kruskal重构树,对于询问点权第 k k k大的点权可以想到主席树。

K r u s k a l Kruskal Kruskal重构树上非叶子结点"管辖"的叶子结点是一段连续的区间,在 d f s dfs dfs的时候,维护每个结点"管辖"的叶子结点区间。

对于询问 ( v , x , k ) (v,x,k) (v,x,k),与上题类似,利用倍增找到结点 u u u,在 u u u管辖的区间中,查询区间第 k k k大。注意离散化

代码:

#include
using namespace std;
typedef long long ll;
const int maxn = 2e5+10;
const int maxm = 5e5+10;
const int INF = 0x3f3f3f3f;
int n, m, q;
int h[maxn];
struct node{ //原图上边 
	int x, y, z;
	bool operator < (const node &b)const{
		return z < b.z;
	}
}edg[maxm];
struct edge{ //重构树上边 
	int to, nxt;
}e[maxn];
int head[maxn], tot;
void add(int a, int b){
	e[++tot].nxt = head[a];
	e[tot].to = b;
	head[a] = tot;
}
struct discreteH{ //高度离散化 
	int h, id;
	bool operator < (const discreteH &b) const{
		return h < b.h;
	}
}dish[maxn];
int fa[maxn];
int val[maxn<<2]; //重构树上点权 
int find(int x){
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}
int nodetot;
void Kruskal(){
	for(int i = 1; i <= n; i++)
		fa[i] = i;
	nodetot = n;
	for(int i = 1; i <= m; i++){
		int fx = find(edg[i].x);
		int fy = find(edg[i].y);
		if(fx == fy)
			continue;
		val[++nodetot] = edg[i].z;
		fa[nodetot] = fa[fx] = fa[fy] = nodetot;
		add(nodetot, fx); add(nodetot, fy);
	}
}
int L[maxn], R[maxn], p[maxn][20], b[maxn];
int dfstot;
void dfs(int u, int fath){
	p[u][0] = fath;
	for(int i = 1; i <= 19; i++)
		p[u][i] = p[p[u][i-1]][i-1];
	L[u] = dfstot;
	if(head[u] == 0){ //叶子即原图上的点 
		b[++dfstot] = u;
		R[u] = dfstot;
		return;
	}
	for(int i = head[u]; i; i = e[i].nxt){
		dfs(e[i].to, u);
	}
	R[u] = dfstot;
}
int roottot;
struct tree{
	int l, r, val;
}t[maxn*40];
int root[maxn];
void pushup(int rt){
	t[rt].val = t[t[rt].l].val + t[t[rt].r].val;
}
void build(int &rt, int l, int r){
	rt = ++roottot;
	if(l == r)
		return;
	int mid = (l+r) >> 1;
	build(t[rt].l, l, mid);
	build(t[rt].r, mid+1, r);
}
void modify(int &rt, int pre, int l, int r, int pos){
	rt = ++roottot;
	t[rt] = t[pre];
	if(l == r){
		t[rt].val ++;
		return;
	}
	int mid = (l+r) >> 1;
	if(pos <= mid)
		modify(t[rt].l, t[pre].l, l, mid, pos);
	else
		modify(t[rt].r, t[pre].r, mid+1, r, pos);
	pushup(rt);
	return;
}
int find_node(int u, int x){
	int v = u;
	for(int i = 19; i >= 0; i--)
		if(val[p[v][i]] <= x)
			v = p[v][i];
	return v;
}
int query(int rt1, int rt2, int l, int r, int k){
	if(l == r){
		if(k == (t[rt1].val - t[rt2].val))
			return l;
		else
			return 0;
	}
	int mid = (l+r) >> 1;
	int d = t[t[rt1].r].val - t[t[rt2].r].val;
	if(k <= d)
		return query(t[rt1].r, t[rt2].r, mid+1, r, k);
	else
		return query(t[rt1].l, t[rt2].l, l, mid, k-d);
}
int main(){
	cin >> n >> m >> q;
	for(int i = 1; i <= n; i++){
		cin >> dish[i].h;
		dish[i].id = i;;
	}
	sort(dish+1, dish+n+1);
	h[0] = INF;
	for(int i = 1; i <= n; i++)
		h[dish[i].id] = i; 
	for(int i = 1; i <= m; i++)
		cin >> edg[i].x >> edg[i].y >> edg[i].z;
	sort(edg+1, edg+1+m);
	Kruskal();
	dfs(nodetot, nodetot);
	build(root[0], 1, n);
	for(int i = 1; i <= dfstot; i++)
		modify(root[i], root[i-1], 1, n, h[b[i]]);
	dish[0].h = -1;
	for(int i = 1; i <= q; i++){
		int v, x, k;
		cin >> v >> x >> k;
		int ance = find_node(v, x);
		int ans = query(root[R[ance]], root[L[ance]], 1, n, k);
		cout << dish[ans].h << endl;
	}
	return 0;
}

你可能感兴趣的:(算法)