LCA
倍增实现快速求出树上两点的公共祖先,实现原理像是树上ST表,先预处理出来每个点的2^n的父亲是谁与它的深度,然后求lca时把两点先都跳到同一高度,如果重合为一个点,则这个点就是最近公共祖先,不然就倍增从大到小跳,直到跳到祖先的儿子节点,然后返回此时节点的父亲节点。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5+7;
int n,m,s,head[N],f[N][22],dep[N],cnt = 1;
struct Edge{int t,len,nxt;};
Edge edge[N*2];
void AddEdge(int f,int t) //前向星建图,洛谷模板卡vector
{
edge[cnt].t = t;
edge[cnt].nxt = head[f];
head[f] = cnt;
cnt++;
}
void DFS(int x, int fa) //dfs预处理
{
f[x][0] = fa;
dep[x] = dep[fa]+1;
for(int i = 1; (1<<i) <= dep[x]; i++)
f[x][i] = f[f[x][i-1]][i-1];
for(int i = head[x]; i; i = edge[i].nxt)
{
int v = edge[i].t;
if(v == fa) continue;
DFS(v, x);
}
}
int LCA(int x, int y) //实现很简单
{
if(dep[x] > dep[y]) // y >= x
swap(x,y);
for(int i = 20; i >= 0; i--)
if(dep[x] <= dep[y] - (1<<i))
y = f[y][i];
if(x == y) return x;
for(int i = 20; i >= 0; i--)
if(f[x][i] != f[y][i])
x = f[x][i], y = f[y][i];
return f[x][0];
}
int main()
{
scanf("%d %d %d",&n,&m,&s);
for(int t1,t2,i = 1; i < n; i++)
{
scanf("%d %d",&t1,&t2);
AddEdge(t1,t2);
AddEdge(t2,t1);
}
DFS(s,0);
for(int t1,t2,i = 1; i <= m; i++)
{
scanf("%d %d",&t1,&t2);
printf("%d\n",LCA(t1,t2));
}
return 0;
}
题目
模板题
不需要LCA的题
洛谷P2420 异或
因为根据异或的性质,重复异或两次 = 0,所以直接求两点到根节点的异或和再异或即可,不需要求到lca,
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
int n,m,head[N],vis[N],cnt = 1,dis[N];
struct Edge
{
int t,val,nxt;
};
Edge edge[N*4];
void AddEdge(int f, int t, int val)
{
edge[cnt].t = t;
edge[cnt].val = val;
edge[cnt].nxt = head[f];
head[f] = cnt;
cnt++;
}
void Dfs(int v, int fa, int val)
{
dis[v] = val;
for(int i = head[v]; i; i = edge[i].nxt)
{
int u = edge[i].t;
if(u != fa)
{
Dfs(u, v, val ^ edge[i].val);
}
}
}
int main()
{
scanf("%d",&n);
for(int t1,t2,t3,i = 1; i < n; i++)
{
scanf("%d %d %d",&t1,&t2,&t3);
AddEdge(t1, t2, t3);
AddEdge(t2, t1, t3);
}
Dfs(1, 0, 0);
scanf("%d",&m);
for(int t1,t2,i = 1; i <= m; i++)
{
scanf("%d %d",&t1,&t2);
printf("%d\n",dis[t1] ^ dis[t2]);
}
return 0;
}
LCA+最大(小)生成树
洛谷P1967 火车运输
因为不是一棵树,但是要求最大边权值,当然是要先跑一遍最大生成树,因为是两个图,建议写结构体存图。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+7;
const int INF = 0x3f3f3f3f;
struct Edge1{int x,y,dis;}edge1[MAXN];
struct Edge2{int to,next,w;}edge2[MAXN];
int cnt,n,m,head[MAXN],deep[MAXN],f[MAXN],fa[MAXN][21],w[MAXN][21];
bool vis[MAXN];
void addedge(int from, int to, int w)
{
edge2[++cnt].next=head[from];
edge2[cnt].to=to;
edge2[cnt].w=w;
head[from]=cnt;
return ;
}
bool CMP(Edge1 x, Edge1 y)
{
return x.dis>y.dis;
}
int find(int x)
{
if(f[x]!=x) f[x]=find(f[x]);
return f[x];
}
void kruskal()
{
sort(edge1+1, edge1+m+1, CMP);
for(int i=1; i<=n; i++)
f[i]=i;
for(int i=1; i<=m; i++)
if(find(edge1[i].x)!=find(edge1[i].y))
{
f[find(edge1[i].x)]=find(edge1[i].y);
addedge(edge1[i].x, edge1[i].y, edge1[i].dis);
addedge(edge1[i].y, edge1[i].x, edge1[i].dis);
}
return ;
}
void dfs(int node)
{
vis[node]=true;
for(int i=head[node]; i; i=edge2[i].next)
{
int to=edge2[i].to;
if(vis[to]) continue;
deep[to]=deep[node]+1;
fa[to][0]=node;
w[to][0]=edge2[i].w;
dfs(to);
}
return ;
}
int lca(int x, int y)
{
if(find(x)!=find(y)) return -1;
int ans=INF;
if(deep[x]>deep[y]) swap(x,y);
for(int i=20; i>=0; i--)
if(deep[fa[y][i]]>=deep[x])
{
ans=min(ans, w[y][i]);
y=fa[y][i];
}
if(x==y) return ans;
for(int i=20; i>=0; i--)
if(fa[x][i]!=fa[y][i])
{
ans=min(ans, min(w[x][i], w[y][i]));
x=fa[x][i];
y=fa[y][i];
}
ans=min(ans, min(w[x][0], w[y][0]));
return ans;
}
int main()
{
int x,y,z,q;
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++)
{
scanf("%d%d%d",&x,&y,&z);
edge1[i].x=x;
edge1[i].y=y;
edge1[i].dis=z;
}
kruskal();
for(int i=1; i<=n; i++)
if(!vis[i])
{
deep[i]=1;
dfs(i);
fa[i][0]=i;
w[i][0]=INF;
}
for(int i=1; i<=20; i++)
for(int j=1; j<=n; j++)
{
fa[j][i]=fa[fa[j][i-1]][i-1];
w[j][i]=min(w[j][i-1], w[fa[j][i-1]][i-1]);
}
scanf("%d",&q);
for(int i=1; i<=q; i++)
{
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}
LCA+树上差分
洛谷P3128 最大流
用树上差分,树上点差分,即每个点的真实值是所有儿子节点更新后的值即儿子节点真实值加上自己的差分值。在树上一段路径每个点权值+1即路径左右两端差分值+1,因为求到lca时lca+2了,但是lca也在路径上,所以要减去一个1,然后在lca的父亲节点-1。
具体是这样的:假设一条u到v的路径,那么这条路径是u—>lca(u,v)—>v的,所以我们把u—>lca(u,v)与lca(u,v)—>v两条路径各自加一,也就是++power[u],++power[v],power[lca(u,v)]-=2
但是这样一来,lca(u,v)上+2又-2等于0,也就是u—>v整条路经上除了lca(u,v)都加了1,为了排除这个干扰,我们把power[lca(u,v)]-=2改成- -power[lca(u,v)],- -power[lca(u,v)的父亲]
LCA用倍增比较方便,最后遍历整棵树统计和
#include <bits/stdc++.h>
using namespace std;
const int N = 5e4+7;
int n,k,head[N],cnt,f[N][22],w[N],dep[N],sum[N],ans;
struct Edge
{
int f, t, vis, nxt;
};
Edge e[N<<2];
void AddEdge(int _f, int _t)
{
e[++cnt].t = _t;
e[cnt].nxt = head[_f];
head[_f] = cnt;
}
void DFS(int u, int fa)
{
f[u][0] = fa;
dep[u] = dep[fa] + 1;
for(int i = 0; f[u][i]; i++)
f[u][i+1] = f[f[u][i]][i];
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].t;
if(v != fa)
DFS(v, u);
}
}
int LCA(int x, int y)
{
//注意顺序!! 这样写的一定要从20->0不然会跳不必要的二进制小位数
if(dep[x] < dep[y]) //dep[x] >= dep[y]
swap(x, y);
for(int i = 20; i >= 0; i--)
{
if(dep[x] - (1<<i) >= dep[y])
x = f[x][i];
}
if(x == y) return x;
for(int i = 20; i >= 0; i--)
{
if(f[x][i] != f[y][i])
x = f[x][i], y = f[y][i];
}
return f[x][0];
}
void GetAns(int u, int fa)
{
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].t;
if(fa == v) continue;
GetAns(v, u);
w[u] += w[v];
ans = max(ans, w[u]);
}
}
int main()
{
scanf("%d %d",&n,&k);
for(int t1,t2,i = 1; i < n; i++)
{
scanf("%d %d",&t1,&t2);
AddEdge(t1, t2);
AddEdge(t2, t1);
}
DFS(1, 0);
for(int t1,t2,i = 1; i <= k; i++)
{
scanf("%d %d",&t1,&t2);
int lca = LCA(t1, t2);
w[t1]++,w[t2]++,w[lca]--,w[f[lca][0]]--;
}
GetAns(1, 0);
printf("%d\n",ans);
return 0;
}