「刷题笔记」LCA问题相关

2023-07-30,,

板子

ll lg[40];

ll dep[N],fa[N][40];
ll dis[N];
void dfs(ll u,ll f)
{
dep[u]=dep[f]+1;
fa[u][0]=f;
for(int i=1;i<=lg[dep[u]];i++)
{
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(int i=head[u];i;i=next[i])
{
ll v=e[i].v;
if(v==f)continue;
dis[v]=dis[u]+e[i].w;
dfs(v,u);
}
} ll lca(ll a,ll b)
{
if(dep[a]<dep[b])swap(a,b);
while(dep[a]>dep[b])
{
a=fa[a][lg[dep[a]-dep[b]]];
}
if(a==b)return a;
for(int i=lg[dep[a]];i>=0;i--)
{
if(fa[a][i]!=fa[b][i])
{
a=fa[a][i];
b=fa[b][i];
}
}
return fa[a][0];
}
void lgp()
{
for(int i=2;i<=n;i++)
{
lg[i]=lg[i-1]+((2<<lg[i-1])==i);
}
}

牧场旅行

记一个比较容易的结论:

树上路径长:

\[dis(u,v)=dis(1,u)+dis(1,v)-2\cdot dis[1,lca(u,v)]
\]

这道题就是用这个结论套\(lca\)板子

然后注意写不要在\(if\)语句的同行加分号……(就是直接判了个寂寞)

Distance Queries 距离咨询

怕不是和牧场旅行完全重题……

记下是因为要注意预处理\(lg\)数组的时候是从\(1\)开始的

然后输入的方向根本没用……

货车运输&&星际导航

这两题就只是最大最小相反好吧……

之前学树剖的时候做过货车这道题,树剖方法见这篇博客

这里记一下倍增方法

首先容易想到这条符合题意的路径肯定是在无向图的最大/最小生成树上的,于是先用\(kruskal\)把这棵树抠出来,然后树上倍增求即可

相当于一个树上的\(RMQ\),所以用倍增解决,跟求\(lca\)时差不了太多,就是多维护了一个区间最值数组,然后跳的时候顺便记得用它更新答案

再就是\(kruskal\)之前先把边存在别的地方,要不会出玄学错误……

代码(以货车运输为例)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define ZZ_zuozhe int main()
#define N 50005
#define E 500005 ll n,m,a,b,c,q; struct edge
{
ll u,v,w;
}e[E],t[E];
ll tot=0,head[E],next[E];
void add(ll a,ll b,ll c)
{
++tot;
e[tot].u=a;
e[tot].v=b;
e[tot].w=c;
next[tot]=head[a];
head[a]=tot;
}
bool cmp(edge a,edge b)
{
return a.w>b.w;
} ll lg[E];
void lgp()
{
for(int i=2;i<=m;i++)lg[i]=lg[i>>1]+1;
} ll dep[N],fa[N][30],mx[N][30]; void dfs(ll u,ll f)
{
dep[u]=dep[f]+1;
fa[u][0]=f;
for(int i=1;i<=lg[dep[u]];i++)
{
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(int i=head[u];i;i=next[i])
{
ll v=e[i].v;
if(v==f)continue;
mx[v][0]=e[i].w;
dfs(v,u);
}
} void mxp()
{
for(int j=1;j<=lg[n];j++)
{
for(int i=1;i<=n;i++)
{
mx[i][j]=min(mx[i][j-1],mx[fa[i][j-1]][j-1]);
}
}
} ll f[N];
ll fd(ll a)
{
if(f[a]==a)return a;
else return f[a]=fd(f[a]);
}
void uni(ll a,ll b)
{
ll x=fd(a),y=fd(b);
f[y]=x;
} void kruskal()
{
sort(t+1,t+m+1,cmp);
memset(head,0,sizeof head);
tot=0;
for(int i=1;i<=n;i++)f[i]=i;
for(int i=1;i<=m;i++)
{
ll a=t[i].u,b=t[i].v,c=t[i].w;
if(fd(a)!=fd(b))
{
uni(a,b);
add(a,b,c);
add(b,a,c);
}
}
} ll lca(ll a,ll b)
{
ll res=1000000000000000000;
if(dep[a]<dep[b])swap(a,b);
while(dep[a]>dep[b])
{
res=min(res,mx[a][lg[dep[a]-dep[b]]]);
a=fa[a][lg[dep[a]-dep[b]]];
}
if(a==b)return res;
for(int i=lg[dep[a]];i>=0;i--)
{
if(fa[a][i]!=fa[b][i])
{
res=min(res,mx[a][i]);
res=min(res,mx[b][i]);
a=fa[a][i];
b=fa[b][i];
}
}
res=min(res,mx[a][0]);
res=min(res,mx[b][0]);
return res;
} ZZ_zuozhe
{
//freopen("owo.in","r",stdin);
//freopen("awa.out","w",stdout);
scanf("%lld%lld",&n,&m);
lgp();
for(int i=1;i<=m;i++)
{
scanf("%lld%lld%lld",&t[i].u,&t[i].v,&t[i].w);
}
kruskal();
dep[0]=0;
for(int i=1;i<=n;i++)if(!dep[i])dfs(i,0);
mxp();
scanf("%lld",&q);
for(int i=1;i<=q;i++)
{
scanf("%lld%lld",&a,&b);
if(fd(a)!=fd(b))puts("-1");
else printf("%lld\n",lca(a,b));
}
return 0;
}

bzoj3306 树

就是遥远的国度嘛\(qaq\)

这篇博客里有思路和树剖的版本,这里给出倍增方法的

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define ZZ_zuozhe int main()
#define N 200005 ll n,q,a,b,root,rt;
char ch[3]; struct edge
{
ll u,v;
}e[N];
ll tot=0,head[N],next[N];
void add(ll a,ll b)
{
++tot;
e[tot].u=a;
e[tot].v=b;
next[tot]=head[a];
head[a]=tot;
} ll lg[N];
void lgp()
{
for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
} ll dep[N],fa[N][30];
ll l[N],r[N],cnt=0;
ll num[N],w[N]; void dfs(ll u,ll f)
{
l[u]=++cnt;
num[l[u]]=w[u];
fa[u][0]=f;
dep[u]=dep[f]+1;
for(int i=1;i<=lg[dep[u]];i++)
{
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(int i=head[u];i;i=next[i])
{
ll v=e[i].v;
if(v==f)continue;
dfs(v,u);
}
r[u]=cnt;
} ll lca(ll a,ll b)
{
if(dep[a]<dep[b])swap(a,b);
while(dep[a]>dep[b])
{
a=fa[a][lg[dep[a]-dep[b]]];
}
if(a==b)return a;
for(int i=lg[dep[a]];i>=0;i--)
{
if(fa[a][i]!=fa[b][i])
{
a=fa[a][i];
b=fa[b][i];
}
}
return fa[a][0];
} #define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r
ll tree[N<<2]; void upd(ll rt)
{
tree[rt]=min(tree[rt<<1],tree[rt<<1|1]);
} void build(ll rt,ll l,ll r)
{
if(l==r)
{
tree[rt]=num[l];//cout<<l<<' '<<tree[rt]<<endl;
return;
}
ll m=(l+r)>>1;
build(lson);
build(rson);
upd(rt);
} void change(ll rt,ll l,ll r,ll n,ll val)
{
if(l==r)
{
tree[rt]=val;
return;
}
ll m=(l+r)>>1;
if(m>=n)change(lson,n,val);
if(m<n)change(rson,n,val);
upd(rt);
} ll query(ll rt,ll l,ll r,ll nl,ll nr)
{
if(nl<=l&&r<=nr)
{
//cout<<' '<<tree[rt]<<endl;
return tree[rt];
}
ll m=(l+r)>>1;
ll ans=1000000000000000000;
if(m>=nl)ans=min(ans,query(lson,nl,nr));
if(m<nr)ans=min(ans,query(rson,nl,nr));
return ans;
} ZZ_zuozhe
{
//freopen("owo.in","r",stdin);
scanf("%lld%lld",&n,&q);
lgp();
for(int i=1;i<=n;i++)
{
scanf("%lld%lld",&a,&w[i]);
if(a)add(a,i);
if(a==0)root=i;
}
dfs(root,0);
rt=root;
build(1,1,n);
for(int i=1;i<=q;i++)
{
scanf("%s",ch);
if(ch[0]=='V')
{
scanf("%lld%lld",&a,&b);
//cout<<'\t'<<l[a]<<' '<<b<<endl;
change(1,1,n,l[a],b);
}
else if(ch[0]=='E')
{
scanf("%lld",&rt);
}
else if(ch[0]=='Q')
{
scanf("%lld",&a);
//cout<<' '<<a<<' '<<rt<<endl;
if(a==rt)printf("%lld\n",tree[1]);
else if(l[a]>l[rt]||r[a]<l[rt])printf("%lld\n",query(1,1,n,l[a],r[a]));
else
{
ll now=rt;
for(int i=16;i>=0;i--)
{
if(dep[fa[now][i]]>dep[a])now=fa[now][i];
}
printf("%lld\n",min(query(1,1,n,1,l[now]-1),query(1,1,n,r[now]+1,n)));
}
}
}
}

「刷题笔记LCA问题相关的相关教程结束。

《「刷题笔记」LCA问题相关.doc》

下载本文的Word格式文档,以方便收藏与打印。