LCA-倍增法(写给自己看)
LCA-倍增法
题目又做不下去来写题解了
算法思想
- 类似于ST表的思想维护dp[i][j]:节点i向上跳2^j高度的的节点
- 转移方程 \(dp[i][j]=dp[dp[i][j-1]][j-1]\)注意理解依赖关系,dp[i][j]状态的确定需要dp[x][j-1]
- 所以在每次需要先得到[j-1]的所有状态才能推出[j],即如果写成双重循环j的循环应该在外层
- dfs中完成初始化\(dp[i][0]\),同时可完成dp[i][j]的更新
用途
- 多次查询两点间路径(维护树上前缀和即可)
- 裸题:最近公共祖先
- 维护树上两点间路径的信息(如min,max),方法如同维护dp[i][j]只要能从info[i][j-1],info[dp[i][j-1]][j-1]->info[i][j]即可,注意dp和info的顺序
- 维护两点路径间区间变化信息
模板
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define name2str(name) (#name)
#define db(x) cout<<#x"=["<<(x)<<"]"<<endl
#define CL(a,b) memset(a,b,sizeof(a))
#define sf(a) scanf("%d",&a)
#define pr(a) printf("%d\n",a)
#define rng(a) a.begin(),a.end()
#define pb push_back
#define fast ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define fr0(i,m) for(int i=0;i<m;i++)
#define fr1(i,m) for(int i=1;i<=m;i++)
//author:fridayfang
//date:19 3月 03
const double esp=1e-8;
const int mod=1e9+7;
const double pi=acos(-1);
const int inf=0x3f3f3f3f;
const int maxn = 5e5 + 5;
const int maxm = 1e6+5;
int dep[maxn],fa[maxn][25],head[maxn];
int n,m,s,cnt,root;
struct Edge{
int v,nxt;
Edge(){}
Edge(int _v,int _nxt):v(_v),nxt(_nxt){}
}es[maxm];
void add(int u,int v){
es[cnt].v=v,es[cnt].nxt=head[u],head[u]=cnt,cnt++;
}
void dfs(int r,int father){
dep[r]=dep[father]+1;
fa[r][0]=father;
//dp: fa[i][j]=fa[fa[i][j-1]][j-1]
for(int i=1;(1<<i)<=dep[r];i++) fa[r][i]=fa[fa[r][i-1]][i-1];
for(int t=head[r];t!=-1;t=es[t].nxt){
int v=es[t].v;
if(v==father) continue;
else dfs(v,r);
}
}
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
//x深,先爬到和y深度相同
while(dep[x]>dep[y]){x=fa[x][(int)log2(dep[x]-dep[y])];}
if(x==y) return x;
//统一一起跳
for(int i=(int)log2(dep[x]);i>=0;i--){
if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
}
return fa[x][0];
}
int main(){
CL(head,-1);
sf(n),sf(m),sf(s);
fr1(i,n-1){int u,v;sf(u),sf(v);add(u,v),add(v,u);}
dfs(s,0);
fr1(i,m){int x,y;sf(x),sf(y);pr(lca(x,y));}
return 0;
}
有趣的题目
luogu1967
- 给一个图,多次询问a-b,求a-b之间所有路径中限重最大的(一条路径中限重最小的那一段是这条路径的限重限重)
- 建最大生成树,并维护path[i][j]表示从节点i向上跳2^j步的边的min
- 求lca是即能求出答案
- code如下
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define name2str(name) (#name)
#define db(x) cout<<#x"=["<<(x)<<"]"<<endl
#define CL(a,b) memset(a,b,sizeof(a))
#define sf(a) scanf("%d",&a)
#define pr(a) printf("%d\n",a)
#define rng(a) a.begin(),a.end()
#define pb push_back
#define fast ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define fr0(i,m) for(int i=0;i<m;i++)
#define fr1(i,m) for(int i=1;i<=m;i++)
//author:fridayfang
//date:19 3月 03
const double esp=1e-8;
const int mod=1e9+7;
const double pi=acos(-1);
const int inf=0x3f3f3f3f;
const int maxn = 1e5 + 5;
const int maxm = 1e5+5;
struct eg{
int u,v,dis;
bool friend operator<(eg e1,eg e2){
return e1.dis>e2.dis;
}
}egs[maxn];//for kruscal
struct Edge{
int v,nxt,w;
}es[maxn];//rebuild a graph
int n,m,cnt;
int head[maxn],f[maxn],fa[maxn][21],w[maxn][21],dep[maxn];
void adde(int u,int v,int w){
es[cnt].v=v,es[cnt].w=w,es[cnt].nxt=head[u];
head[u]=cnt,cnt++;
}
//w,f同时更新,表示节点i向上跳2^j 的节点和负重
int find(int u){return f[u]==u?u:f[u]=find(f[u]);}
void kruscal(){
int cnte=0;
CL(head,-1);
sort(egs,egs+m);
//db(egs[0].dis);
for(int i=0;i<m;i++){
int u=egs[i].u,v=egs[i].v,dis=egs[i].dis;
int x=find(u),y=find(v);
if(x!=y){
//db(u),db(v),db(dis);
cnte++;
adde(u,v,dis),adde(v,u,dis);//建立最大生成树
//printf("%d %d %d\n",u,v,dis);
f[x]=y;
}
}
//db(cnte);
}
void dfs(int r,int fath,int we){//dfs 更新f[][],w[][]
//db(r),db(fath),db(we);
dep[r]=dep[fath]+1;
fa[r][0]=fath,w[r][0]=we;
for(int j=1;(1<<j)<=dep[r];j++){
fa[r][j]=fa[fa[r][j-1]][j-1];
w[r][j]=min(w[fa[r][j-1]][j-1],w[r][j-1]);
//db(j),db(fa[r][j]),db(w[r][j]);
}
for(int t=head[r];t!=-1;t=es[t].nxt){
int v=es[t].v,w=es[t].w;
if(v!=fath){
dfs(v,r,w);//应该没问题??
}
}
}
int lca(int x,int y){
//db(x),db(y);
if(find(x)!=find(y)) return -1;
if(dep[x]==0) dfs(x,0,inf);//dep[] 代表之前没有dfs
int ans=inf;
//db(dep[x]),db(dep[y]);
if(dep[x]<dep[y])swap(x,y);
//db(dep[x]),db(dep[y]);
while(dep[x]>dep[y]){
int len=(int)log2(dep[x]-dep[y]);
//db(w[x][len]),db(ans);
ans=min(ans,w[x][len]);
x=fa[x][len];
}
//db(x),db(y),db(ans);
if(x==y){return ans;}
//db((int)log2(dep[x]));
for(int j=(int)log2(dep[x]);j>=0;j--){//fa[][]初始化为0,即使跳过了也为0
if(fa[x][j]!=fa[y][j]){
//db(fa[x][j]),db(j);
ans=w[x][j]<w[y][j]?min(w[x][j],ans):min(w[y][j],ans);
x=fa[x][j],y=fa[y][j];
}
}
//还要向上爬一步
ans=min(ans,w[x][0]);ans=min(ans,w[y][0]);
return ans;
}
int main(){
sf(n),sf(m);
fr1(i,n)f[i]=i;
fr0(i,m){int u,v,w;sf(u),sf(v),sf(w);egs[i].u=u,egs[i].v=v,egs[i].dis=w;}
kruscal();
int s;sf(s);
fr1(i,s){int x,y;sf(x),sf(y);pr(lca(x,y));}
return 0;
}
cf609e
- 与上面题目很像,思路应该有借鉴的成分
- 给一个无向图,求出包含u-v边的最小生成树的权值和
- 不在最小生成树上的边(x,y)>=从x到y在树上路径最重的边
Some spanning tree is minimal if and only if the weight of any other edge (x, y) (not from spanning tree) is not less than the weight of the heaviest edge on the path from x to y in spanning tree.
- MST, 维护路径上最重的边(同上),将(x-y)替换掉最重的边
- 真的是很好的题目
#include <bits/stdc++.h>
using namespace std;
#pragma GCC optimize("O3")
#define ll long long
#define max(a,b) (a)>(b)?(a):(b)
#define min(a,b) (a)<(b)?(a):(b)
#define name2str(name) (#name)
#define db(x) cout<<#x"=["<<(x)<<"]"<<endl
#define CL(a,b) memset(a,b,sizeof(a))
#define sf(a) scanf("%d",&a)
#define pr(a) printf("%d\n",a)
#define rng(a) a.begin(),a.end()
#define pb push_back
#define fast ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define fr0(i,m) for(int i=0;i<m;i++)
#define fr1(i,m) for(int i=1;i<=m;i++)
//author:fridayfang
//date:19 3月 07
const double esp=1e-8;
const int mod=1e9+7;
const double pi=acos(-1);
const int inf=0x3f3f3f3f;
const int maxn = 2e5 + 5;
const int maxm = 4e5+5;
int head[maxn],fa[maxn];
int dp[maxn][20],dep[maxn];
int heavy[maxn][20];//维护向上最重的边
int cnt,n,m;
struct Edge{
int u,v,nxt,w;
Edge(){}
Edge(int _u,int _v,int _w):u(_u),v(_v),w(_w),nxt(-1){}
}es[maxm],es2[maxm],es3[maxm];
void adde(int u,int v,int w){
es[cnt].u=u,es[cnt].v=v,es[cnt].w=w,es[cnt].nxt=head[u],head[u]=cnt,cnt++;
}
//es2 kruscal算法
bool cmp(Edge e1,Edge e2){return e1.w<e2.w;}
int find(int u){return fa[u]==u?u:fa[u]=find(fa[u]);}
ll kruscal(){
fr1(i,n)fa[i]=i;
sort(es2,es2+m,cmp);
int cnte=0;
ll len=0;
for(int i=0;i<m;i++){
int u=es2[i].u,v=es2[i].v,w=es2[i].w;
int x=find(u),y=find(v);
if(x!=y){
cnte++;
fa[x]=y;len+=w;
adde(u,v,w),adde(v,u,w);
}
if(cnte>=n-1)break;
}
return len;
//build a tree
}
void dfs(int u,int father,int d,int wei){
dep[u]=d;dp[u][0]=father,heavy[u][0]=wei;
for(int i=1;(1<<i)<=dep[u];i++)dp[u][i]=dp[dp[u][i-1]][i-1];
for(int i=1;(1<<i)<=dep[u];i++)heavy[u][i]=max(heavy[u][i-1],heavy[dp[u][i-1]][i-1]);
for(int t=head[u];t!=-1;t=es[t].nxt){
int u=es[t].u,v=es[t].v,w=es[t].w;
if(v!=father)dfs(v,u,d+1,w);
}
}
int lca(int u,int v){//u-lac(u,v)-v这条链上的最大权重
int maxlen=0;
if(dep[u]<dep[v])swap(u,v);
while(dep[u]>dep[v]){
maxlen=max(maxlen,heavy[u][(int)log2(dep[u]-dep[v])]);
u=dp[u][(int)log2(dep[u]-dep[v])];
}
//dep[u]=dep[v]
if(u==v) return maxlen;
for(int j=18;j>=0;j--){
if(dp[u][j]!=dp[v][j]){
maxlen=max(maxlen,max(heavy[u][j],heavy[v][j]));
u=dp[u][j],v=dp[v][j];
}
}
maxlen=max(maxlen,max(heavy[u][0],heavy[v][0]));
return maxlen;
}
int main(){
sf(n),sf(m);
CL(head,-1);
fr0(i,m){
int u,v,w;sf(u),sf(v),sf(w);es2[i]=Edge(u,v,w),es3[i]=Edge(u,v,w);
}
ll minlen=kruscal();
int root=1;//根节点
dfs(root,0,1,0);
//db(minlen);
fr0(i,m){
int u=es3[i].u,v=es3[i].v,w=es3[i].w;
int maxlen=lca(u,v);
//db(maxlen),db(w);
ll ans=minlen-maxlen+w;
printf("%lld\n",ans);
}
return 0;
}
hdu2586
- 倍增法好像会T
- 可直接DFS,因为询问很少
- 可能tarjan离线算法可做,可是我不会
- poj也有几道水题,不一一记录,大概是求路径和dis[u]+dis[v]-2*dis[lca(u,v)]
- 还有边权,点权修改的题目...(挖坑)
LCA-倍增法(写给自己看)的更多相关文章
- LCA(最近公共祖先)——LCA倍增法
一.前人种树 博客:最近公共祖先 LCA 倍增法 博客:浅谈倍增法求LCA 二.沙场练兵 题目:POJ 1330 Nearest Common Ancestors 代码: const int MAXN ...
- POJ - 1330 Nearest Common Ancestors(dfs+ST在线算法|LCA倍增法)
1.输入树中的节点数N,输入树中的N-1条边.最后输入2个点,输出它们的最近公共祖先. 2.裸的最近公共祖先. 3. dfs+ST在线算法: /* LCA(POJ 1330) 在线算法 DFS+ST ...
- luogu3379 【模板】最近公共祖先(LCA) 倍增法
题目大意:给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 整体步骤:1.使两个点深度相同:2.使两个点相同. 这两个步骤都可用倍增法进行优化.定义每个节点的Elder[i]为该节点的2^k( ...
- hdu2586 lca倍增法
倍增法加了边的权值,bfs的时候顺便把每个点深度求出来即可 #include<iostream> #include<cstring> #include<cstdio> ...
- poj1470 LCA倍增法
倍增法模板题 #include<iostream> #include<cstring> #include<cstdio> #include<queue> ...
- 最近公共祖先 LCA 倍增法
[简介] 解决LCA问题的倍增法是一种基于倍增思想的在线算法. [原理] 原理和同样是使用倍增思想的RMQ-ST 算法类似,比较简单,想清楚后很容易实现. 对于每个节点u , ancestors[u] ...
- LCA—倍增法求解
LCA(Least Common Ancestors) 即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先. 常见解法一般有三种 这里讲解一种在线算法-倍增 首先我们定义fa[u][j ...
- LCA - 倍增法去求第几个节点
You are given a tree (an undirected acyclic connected graph) with N nodes, and edges numbered 1, 2, ...
- POJ 1330(LCA/倍增法模板)
链接:http://poj.org/problem?id=1330 题意:q次询问求两个点u,v的LCA 思路:LCA模板题,首先找一下树的根,然后dfs预处理求LCA(u,v) AC代码: #inc ...
随机推荐
- js实现cookie有效期至当次日凌晨
实际开发中有要求用户一些行为每天一次,次日开始重新回复功能,一般前端都是通过cookie来记住用户的操作,然后进行判断当日是否还有机会,这时候需要给存储的cookie值一个有效期,让次日自动失效,重新 ...
- EurekaLog是什么鬼?
D的all工程文件打开后,莫名其妙就处于等待打开状态.因为最后一次调整是安装了RO9.0.所以一直怀疑是RO的原因.再加上win7授权问题,安装RO一直不顺当.所以折腾的时间最多. 其他把RO全部卸载 ...
- Spring Cloud Sleuth(十四)
作用 再微服务中 服务调用服务很常见.服务中相互调用链路追踪的尤为重要,能够帮助我们再异常时分析出哪个服务出了异常.以及各个链路中相互调用所消耗时间,通过这些数据能够帮助我们分析出各个服务的性能瓶颈 ...
- sublime text3环境与工具搭建
1,ctrl+shift+P ,打开包安装窗口如下: 2,选择 install Package,安装详解插件 1-安装 JsFormat插件,用于格式化js的插件,使用快捷键 Ctrl+Alt+F对J ...
- 部署WAR包实时查看Tomcat的状态和日志
在不重启Tomcat的情况下部署WAR包实时输出日志的方法: 注意:以下方式只适合Linux. 一.定位错误 查看Tomcat日志的尾部 tail -n 50 /opt/tomcat8/logs/ca ...
- java反射意义
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制. ...
- Javascript中的原型继承具体解释
js中的继承,是面向对象的知识,由于js没有类的概念.所以继承是通过对象实现的.谈到继承.就必须说到prototype,就不得不先说下new的过程. 一个小小的列子: <script type= ...
- 关于Mysql Enterprise Audit plugin的使用
正如之前看到的一篇文章,假设想要知道是谁登陆了你的数据库server,干了什么东西,那么你须要使用Mysql Enterprise Audit plugin. 以下介绍一下Mysql Enterpri ...
- ORM进阶:Hibernate框架搭建及开发
本节将開始.使用hibernate搭建持久层.当然在决定用不用之前,还请斟酌一下是否使用.了解一下Hibernate的优缺点. Hibernate优劣对照 Hibernate是一个持久的ORM框架.首 ...
- tomcat下载及启动
http://tomcat.apache.org/ 打开网页,在左边选择版本,选择后网页往下面拉 拉下来,根据windows选择32还是64位的,其中zip是windows免安装版 下载后解压,然后配 ...