LCA及树上倍增

Lca

定义:对于两个点,他们的最近公共祖先

  1. 是他们的祖先(或自己)
  2. 距离最近

\(f_{i,j}\)表示从节点i向上跳\(2^j\)步能到达的节点。

  1. 将较深的点跳到深度相同。
  2. 两个节点一起跳,直到相同。

如图:3和8的最近公共祖先是1。
树的深度为5,\(\log_2 5=3\)。更深的是8。
\(f_{8,3}=1,f_{8,2}=1,f_{8,1}=5\)。于是8跳到5。
\(f_{5,0}=2\)。2与3齐平。
于是两个一起往上跳,得到公共祖先1。
时间复杂度\(O(\log n)\)

其次,关于\(f\)数组的初始化:
运用dfs序遍历,运用公式\(f_{u,i}=f_{f_{u,i-1},i-1}\)
时间复杂度\(O(n\log n)\)

#include <algorithm>
#include <cstdio>
using std::swap;

const int MAXN = 100010;
const int LOGN = 17;

struct EDGE { int v; EDGE *n; } edge[MAXN*2], *head[MAXN];
int n, m, p, f[MAXN][LOGN], depth[MAXN];

void AddEdge(const int u, const int v) {
	edge[p].n = head[u];
	edge[p].v = v;
	head[u] = edge + p;
	p ++;
}

void dfs(const int u, const int father) {
	depth[u] = depth[father] + 1;

	f[u][0] = father;
	for (int i = 1; i < LOGN; i++)
		f[u][i] = f[ f[u][i - 1] ][i - 1];

	for (EDGE *e = head[u]; e; e = e->n)
		if (e->v != father)
			dfs(e->v, u);
}

int lca(int u, int v) {
	if (depth[u] > depth[v])
		swap(u, v);

	for (int i = LOGN - 1; i >= 0; i--)
		if (depth[ f[v][i] ] >= depth[u])
			v = f[v][i];

	for (int i = LOGN - 1; i >= 0; i--) {
		int s = f[u][i];
		int t = f[v][i];

		if (s != t) {
			u = s;
			v = t;
		}
	}

	if (u == v)
		return u;
	else
		return f[u][0];  
}

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1, u, v; i < n; i++) {
		scanf("%d %d", &u, &v);
		AddEdge(u, v);
		AddEdge(v, u);
	}

	dfs(1, 1);

	while (m--) {
		int u, v;
		scanf("%d %d", &u, &v);

		printf("%d\n", lca(u, v));
	}

	return 0;
}

LCA的应用

P3128 [USACO15DEC]Max Flow P

LCA加树上(点)差分

如图,有一条路径4到9,那么4,9的公共祖先是2,所以这条路径相当于4->2以及5->9。
在这两条路径上进行差分,所以节点4,9加一,节点1,2减一。
其中1是2的祖先,2是5的祖先。

而最后,我们如何才能计算每个节点对应的值呢?
我们只需从叶子结点向上传递差分值,每个父节点的值相当于他的儿子们差分值的和加上自己的差分值。

代码实现:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 100001
#define logn 21
#define inf 0x7f7f7f7f
#define Rep(i,a,b) for(int i=(a); i<=(b); ++i)
#define Per(i,a,b) for(int i=(a); i>=(b); --i)
int n,m,f[maxn][logn],depth[maxn];
int head[maxn],ver[maxn<<1],nxt[maxn<<1],tot,u,v;
int c[maxn],dp[maxn],res=-inf;
inline void Addedge(int x,int y) {
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
inline void DFS(int u,int father) {
	depth[u]=depth[father]+1;
	f[u][0]=father;
	Rep(i,1,logn-1)
		f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u]; i; i=nxt[i]) {
		int v=ver[i];
		if(v!=father) DFS(v,u);
	}
}
inline void Back_DFS(int u,int father) {
	for(int i=head[u]; i; i=nxt[i]) {
		int v=ver[i];
		if(v!=father) {
			Back_DFS(v,u);
			dp[u]+=dp[v];
		}
	}
}
inline int Lca(int u,int v) {
	if(depth[u]>depth[v]) swap(u,v);
	Per(i,logn-1,0)
		if(depth[f[v][i]]>=depth[u])
			v=f[v][i];
	Per(i,logn-1,0) {
		int s=f[u][i],t=f[v][i];
		if(s!=t) {u=s; v=t;}
	}
	if(u==v) return u;
	else return f[u][0];
}
int main() {
	scanf("%d%d",&n,&m);
	Rep(i,1,n-1) {
		scanf("%d%d",&u,&v);
		Addedge(u,v); Addedge(v,u);
	}
	DFS(1,1);
	Rep(i,1,m) {
		scanf("%d%d",&u,&v);
		int x=Lca(u,v);
		c[u]++; c[v]++;
		c[x]--; c[f[x][0]]--;
	}
	Rep(i,1,n) dp[i]=c[i];
	Back_DFS(1,1);
	Rep(i,1,n) res=max(res,dp[i]);
	printf("%d\n",res);
	return 0;
}

P6869 [COCI2019-2020#5] Putovanje

树上边的差分:用\(edge_i\)表示与i和i父亲节点连接边的权值

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 200001
#define logn 23
#define inf 0x7f7f7f7f
#define Rep(i,a,b) for(int i=(a); i<=(b); ++i)
#define Per(i,a,b) for(int i=(a); i>=(b); --i)
int n,m,f[maxn][logn],depth[maxn],edge1[maxn],edge2[maxn];
int head[maxn],ver[maxn<<1],nxt[maxn<<1],tot,u,v,w,x;
int c[maxn],dp[maxn];
ll res;
inline void Addedge(int u,int v,int w,int x) {
	ver[++tot]=v;
	edge1[tot]=w;
	edge2[tot]=x;
	nxt[tot]=head[u];
	head[u]=tot;
}
inline void DFS(int u,int father) {
	depth[u]=depth[father]+1;
	f[u][0]=father;
	Rep(i,1,logn-1)
		f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u]; i; i=nxt[i]) {
		int v=ver[i];
		if(v!=father) DFS(v,u);
	}
}
inline void Back_DFS(int u,int father) {
	int e;
	for(int i=head[u]; i; i=nxt[i]) {
		int v=ver[i];
		if(v!=father) {
			Back_DFS(v,u);
			dp[u]+=dp[v];
		} else e=i;
	}
	res=res+min((ll)dp[u]*edge1[e],(ll)edge2[e]);
}
inline int Lca(int u,int v) {
	if(depth[u]>depth[v]) swap(u,v);
	Per(i,logn-1,0)
		if(depth[f[v][i]]>=depth[u])
			v=f[v][i];
	Per(i,logn-1,0) {
		int s=f[u][i],t=f[v][i];
		if(s!=t) {u=s; v=t;}
	}
	if(u==v) return u;
	else return f[u][0];
}
int main() {
	scanf("%d",&n);
	Rep(i,1,n-1) {
		scanf("%d%d%d%d",&u,&v,&w,&x);
		Addedge(u,v,w,x); Addedge(v,u,w,x);
	}
	DFS(1,0);
	Rep(i,1,n-1) {
		int x=Lca(i,i+1);
		c[i]++; c[i+1]++; c[x]-=2;
	}
	Rep(i,1,n) dp[i]=c[i];
	Back_DFS(1,1);
	printf("%lld\n",res);
	return 0;
}

Dark之连锁

我们考虑一个附加边 \((u,v)\),会与主要边中 \(u->v\) 路径所有点形成一个环。
若斩断了 \(u->v\) 上任意一边,那么下一次必须斩断 \((u,v)\)
那么用树上差分给 \(u->v\) 上所有边权值 \(+1\).
若一边权值为0,则斩断任意附加边即可。
若为1,则斩断唯一边。
若大于1,则无论如何图都会联通。

#include<algorithm>
#include<cstdio>
#include<iostream>
using namespace std;
const int N=2e5+10,logn=18;
int head[N],ver[2*N],nxt[2*N],tot,n,m;
int f[N][logn],depth[N],c[N],ans;
void addedge(int x,int y) {
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
void DFS(int u,int father) {
	depth[u]=depth[father]+1; f[u][0]=father;
	for(int i=1; i<logn; i++) f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u]; i; i=nxt[i]) {
		int v=ver[i];
		if(v==father) continue;
		DFS(v,u);
	}
}
int Lca(int u,int v) {
	if(depth[u]<depth[v]) swap(u,v);
	for(int i=logn-1; i>=0; i--)
		if(depth[f[u][i]]>=depth[v]) u=f[u][i];
	if(u==v) return u;
	for(int i=logn-1; i>=0; i--)
		if(f[u][i]!=f[v][i]) {
			u=f[u][i]; v=f[v][i];
		}
	return f[u][0];
}
void solve(int u,int father) {
	for(int i=head[u]; i; i=nxt[i]) {
		int v=ver[i];
		if(v==father) continue;
		solve(v,u);
		c[u]+=c[v];
	}
	if(u==1) return ;
	if(c[u]==1) ans++;
	if(c[u]==0) ans+=m;
}
int main() {
	scanf("%d%d",&n,&m);
	for(int i=1,u,v; i<n; i++) {
		scanf("%d%d",&u,&v);
		addedge(u,v); addedge(v,u);
	}
	DFS(1,0);
	for(int i=1,u,v; i<=m; i++) {
		scanf("%d%d",&u,&v);
		int w=Lca(u,v);
		c[u]++; c[v]++;
		c[w]--; c[w]--;
	}
	solve(1,0);
	printf("%d\n",ans);
	return 0;
}

树上倍增

典型例题

树上任意两点之间一定有且只有一条路径。现在要求这条路径上,边权的最大值。
树上倍增,如同LCA,就是在LCA中多开一个数组,用来记录答案。
数组\(g_{i,j}\)表示节点i向上跳\(2^j\)步中最大的值。如同f数组一般更新。

#include<algorithm>
#include<cstdio>
using std::max;
using std::swap;
#define ll long long
#define maxn 100001
#define logn 17
#define inf 0x7f7f7f7f
#define Rep(i,a,b) for(int i=(a); i<=(b); ++i)
#define Per(i,a,b) for(int i=(a); i>=(b); --i)
int n,m,f[maxn][logn],g[maxn][logn],depth[maxn];
int head[maxn],ver[maxn<<1],nxt[maxn<<1],edge[maxn<<1],tot;
int u,v,w;
inline void Addedge(int x,int y,int z) {
    ver[++tot]=y;
    edge[tot]=z;
    nxt[tot]=head[x];
    head[x]=tot;
}
inline void DFS(int u,int father,int w) {
    depth[u]=depth[father]+1;
    f[u][0]=father; g[u][0]=w;
    Rep(i,1,logn-1) {
    	f[u][i]=f[f[u][i-1]][i-1];
    	g[u][i]=max(g[u][i-1],g[f[u][i-1]][i-1]);
	}
    for(int i=head[u]; i; i=nxt[i]) {
        int v=ver[i],w=edge[i];
        if(v!=father) DFS(v,u,w);
    }
}
inline int Lca(int u,int v) {
	int ans=0;
    if(depth[u]>depth[v]) swap(u,v);
    Per(i,logn-1,0)
        if(depth[f[v][i]]>=depth[u])
            v=f[v][i],ans=max(ans,g[v][i]);
    Per(i,logn-1,0) {
        int s=f[u][i],t=f[v][i];
        if(s!=t) {
        	ans=max(ans,g[u][i]);
			ans=max(ans,g[v][i]);
			u=s; v=t;
		}
    }
    if(u==v) return ans;
    else return max(ans,max(g[u][0],g[v][0]));
}
int main() {
    scanf("%d",&n);
    Rep(i,1,n-1) {
        scanf("%d%d%d",&u,&v,&w);
        Addedge(u,v,w); Addedge(v,u,w);
    }
    DFS(1,1,0);
    scanf("%d",&m);
    Rep(i,1,m) {
        scanf("%d%d",&u,&v);
        printf("%d\n",Lca(u,v));
    }
    return 0;
}

次小生成树

见《算法竞赛进阶指南》P385

#include<algorithm>
#include<cstdio>
using std::max;
using std::min;
using std::sort;
using std::swap;
const int MAXN=1e5,MAXM=3*1e5,LOGN=17;
int n,m,fa[MAXN];
int head[MAXN],nxt[MAXM],ver[MAXM],edge[MAXM],depth[MAXN],tot;
bool vis[MAXM];
long long sum,ans=1e18,f[MAXN][LOGN],g[MAXN][LOGN],g2[MAXN][LOGN];
struct node {
	int x,y;
	long long z;
	bool operator < (const node s) const {
		return z<s.z;
	}
} gra[MAXM];
void Addedge(int x,int y,long long z) {
	ver[++tot]=y;
	edge[tot]=z;
	nxt[tot]=head[x];
	head[x]=tot;
}
int Getf(int x) {
	if(x==fa[x]) return x;
	else return fa[x]=Getf(fa[x]);
}
void DFS(int u,int father,long long w) {
	depth[u]=depth[father]+1;
	f[u][0]=father;
	g[u][0]=w;
	g2[u][0]=-1e18;
	for(int i=1; i<LOGN; i++) {
		f[u][i]=f[f[u][i-1]][i-1];
		g[u][i]=max(g[u][i-1],g[f[u][i-1]][i-1]);
		if(g[u][i-1]>g[f[u][i-1]][i-1]) {
			g2[u][i]=max(g2[u][i-1],g[f[u][i-1]][i-1]);
		}
		if(g[u][i-1]<g[f[u][i-1]][i-1]) {
			g2[u][i]=max(g[u][i-1],g2[f[u][i-1]][i-1]);
		}
		if(g[u][i-1]==g[f[u][i-1]][i-1]) {
			g2[u][i]=max(g2[u][i-1],g2[f[u][i-1]][i-1]);
		}
	}
	for(int i=head[u]; i; i=nxt[i]) {
		int v=ver[i],w=edge[i];
		if(v!=father) DFS(v,u,w);
	}
}
void Lca(int u,int v,long long w) {
	if(depth[u]>depth[v]) swap(u,v);
	for(int i=LOGN-1; i>=0; i--) {
		if(depth[f[v][i]]>=depth[u]) {
			if(w>g[v][i]) ans=min(ans,sum-g[v][i]+w);
			if(w==g[v][i]) ans=min(ans,sum-g2[v][i]+w);
			v=f[v][i];
		}
	}
	if(u==v) return ;
	for(int i=LOGN-1; i>=0; i--) {
		int s=f[u][i],t=f[v][i];
		if(s!=t) {
			if(w>g[v][i]) ans=min(ans,sum-g[v][i]+w);
			if(w==g[v][i]) ans=min(ans,sum-g2[v][i]+w);
			if(w>g[u][i]) ans=min(ans,sum-g[u][i]+w);
			if(w==g[u][i]) ans=min(ans,sum-g2[u][i]+w);
			u=s; v=t;
		}
	}
	if(w>g[v][0]) ans=min(ans,sum-g[v][0]+w);
	if(w==g[v][0]) ans=min(ans,sum-g2[v][0]+w);
	if(w>g[u][0]) ans=min(ans,sum-g[u][0]+w);
	if(w==g[u][0]) ans=min(ans,sum-g2[u][0]+w);
}
int main() {
	scanf("%d%d",&n,&m);
	for(int i=1; i<=m; i++) {
		scanf("%d%d%lld",&gra[i].x,&gra[i].y,&gra[i].z);
	}
	sort(gra+1,gra+1+m);
	for(int i=1; i<=n; i++) fa[i]=i;
	for(int i=1; i<=m; i++) {
		if(gra[i].x==gra[i].y) continue;
		int fx=Getf(gra[i].x),fy=Getf(gra[i].y);
		if(fx==fy) continue;
		Addedge(gra[i].x,gra[i].y,gra[i].z);
		Addedge(gra[i].y,gra[i].x,gra[i].z);
		fa[fx]=fy; vis[i]=1;
		sum+=gra[i].z;
	}
	DFS(1,1,0);
	for(int i=1; i<=m; i++) {
		if(vis[i]||gra[i].x==gra[i].y) continue;
		Lca(gra[i].x,gra[i].y,gra[i].z);
	}
	printf("%lld\n",ans);
	return 0;
}

DFS序

维护子树最大值


DFS序是:12445667752331.
其中,每个元素都出现了两次,第一次是搜索到了加入序列,第二次是回溯到了加入序列。
而这两次之间包含的元素就是它的子树中的元素。
例如:566775 这一段中,所代表的就是子树5中有6,7两号元素。
其中,\(id_0\) 代表它首次出现,\(id_1\) 代表它第二次出现

#include<cstdio>
#include<algorithm>
using std::max;
const int MAXN=100005;
int n,m,p,q,id[MAXN][2];
int ver[MAXN*2],nxt[MAXN*2],head[MAXN],tot;
struct Tree {
	int l,r,dat;
}t[MAXN*2*4];
void Addedge(int x,int y) {
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
void DFS(int u,int father) {
	id[u][0]=++q;
	for(int i=head[u]; i; i=nxt[i]) {
		int v=ver[i];
		if(v!=father) DFS(v,u);
	}
	id[u][1]=++q;
}
void Build(int p,int l,int r) {
	t[p].l=l; t[p].r=r;
	if(l==r) {t[p].dat=0; return ;}
	int mid=(l+r)/2;
	Build(p*2,l,mid); Build(p*2+1,mid+1,r);
	t[p].dat=max(t[p*2].dat,t[p*2+1].dat);
}
void Modify(int p,int x,int v) {
	if(t[p].l==t[p].r) {t[p].dat=v; return ;}
	int mid=(t[p].l+t[p].r)/2;
	if(x<=mid) Modify(p*2,x,v);
	else Modify(p*2+1,x,v);
	t[p].dat=max(t[p*2].dat,t[p*2+1].dat);
}
int Query(int p,int l,int r) {
	if(l<=t[p].l&&r>=t[p].r) return t[p].dat;
	int mid=(t[p].l+t[p].r)/2;
	int val=-(1<<30);
	if(l<=mid) val=max(val,Query(p*2,l,r));
	if(r>mid) val=max(val,Query(p*2+1,l,r));
	return val;
}
int main() {
	scanf("%d",&n);
	for(int i=1,u,v; i<n; i++) {
		scanf("%d%d",&u,&v);
		Addedge(u,v);
		Addedge(v,u);
	}
	DFS(1,1); Build(1,1,q);
	scanf("%d",&m);
	for(int i=1,opt,x,y; i<=m; i++) {
		scanf("%d",&opt);
		if(opt==1) {
			scanf("%d%d",&x,&y);
			Modify(1,id[x][0],y);
		} else {
			scanf("%d",&x);
			printf("%d\n",Query(1,id[x][0],id[x][1]));
		}
	}
	return 0;
}

维护链上元素和

对于一个点的修改,可以看作为它的子树都修改,因为它的子树中元素和根节点连接的链都要经过这个点。
对于查询,只要求出单点的值就可以了。
代码:(树状数组)

#include<cstdio>
#include<algorithm>
const int MAXN=100005;
int n,m,p,q,id[MAXN][2];
int ver[MAXN*2],nxt[MAXN*2],head[MAXN],tot;
int c[MAXN*2];
void Addedge(int x,int y) {
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
void DFS(int u,int father) {
	id[u][0]=++q;
	for(int i=head[u]; i; i=nxt[i]) {
		int v=ver[i];
		if(v!=father) DFS(v,u);
	}
	id[u][1]=++q;
}
void Modify(int p,int y) {
    for(; p<=q; p+=p&(-p))
		c[p]+=y;
}
int Query(int p) {
	int res=0;
	for(; p; p-=p&(-p))
		res+=c[p];
	return res;
}
int main() {
	scanf("%d",&n);
	for(int i=1,u,v; i<n; i++) {
		scanf("%d%d",&u,&v);
		Addedge(u,v);
		Addedge(v,u);
	}
	DFS(1,1);
	scanf("%d",&m);
	for(int i=1,opt,x,y; i<=m; i++) {
		scanf("%d",&opt);
		if(opt==1) {
			scanf("%d%d",&x,&y);
			Modify(id[x][0],y);
			Modify(id[x][1]+1,-y);
		} else {
			scanf("%d",&x);
			printf("%d\n",Query(id[x][0]));
		}
	}
	return 0;
}
posted @ 2022-08-13 13:31  s1monG  阅读(43)  评论(0编辑  收藏  举报