lca最近公共祖先与树上倍增。
https://vjudge.net/contest/295298#problem/A
lca 的题目
求任意两点的距离。
A题是在线算法,用st表rmq来实现。
https://blog.csdn.net/nameofcsdn/article/details/52230548
相当于先把整个树dfs一遍,记录整个dfs过程中的点(可重复,相当于dfs序,按顺序排好所有的点),并且记录每个点第一次被遍历到的得dfs序,
然后两个点的最近公共祖先就是第一次被遍历到的下标之间点深度最小的那个点。
1.最原始的lca(可以遍历到两点到lca之间所有的点,有些题目需要(后来发现并不需要,因为可以树上倍增))
int lca(int u, int v) {
if(dep[u] < dep[v]) swap(u, v);
while(dep[u] > dep[v]) {
u = pa[u];
}//先让两个点到达同一深度。
while(u != v) {
v = pa[v];
u = pa[u];
}//两个点一起向上走,知道走到同一点就是lca。
return u;
}
在线做法复杂度nlogn。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 4e4 + ;
int t;
int n, m;
struct Node {
int v, dis;
Node(int v = , int dis = ) : v(v), dis(dis) {};
};
vector<Node> g[maxn];
int rmq[maxn << ];//记录深度。整个dfs过程中遍历点得到深度。
int dfsn[maxn << ];//记录整个dfs过程中经过的点,总的大小为2 * n - 1;
int fir[maxn];//每个点第一次被dfs到在dfsn数组中的位置。
int dis[maxn];//与根节点之间的的距离
int cnt = ; void dfs(int u, int pre, int dep) {
dfsn[++cnt] = u;
rmq[cnt] = dep;
fir[u] = cnt;
for(int i = ; i < g[u].size(); i++) {
int v = g[u][i].v;
if(v == pre) continue;
dis[v] = dis[u] + g[u][i].dis;
dfs(v, u, dep + );
dfsn[++cnt] = u;
rmq[cnt] = dep;
}
} int lg[maxn << ];
int dp[maxn << ][]; void RMQ(int val) {
lg[] = -;
for(int i = ; i <= val; i++) {
lg[i] = ((i & (i - )) == ) ? lg[i - ] + : lg[i - ];
dp[i][] = i;
}//记录lg值。
for(int j = ; j <= lg[val]; j++) {
for(int i = ; i + ( << j) - <= val; i++) {
dp[i][j] = rmq[dp[i][j - ] ] < rmq[dp[i + ( << (j - ))][j - ] ] ? dp[i][j - ] : dp[i + ( << (j - )) ][j - ]; }
}//rmq得到最小深度的那个点的下标。
} int query(int a, int b) {
if(a > b) swap(a, b);
int k = lg[b - a + ];
return rmq[dp[a][k] ] < rmq[dp[b - ( << k) + ][k] ] ? dp[a][k] : dp[b - ( << k) + ][k];
} int lca_query(int u, int v) {
return dfsn[query(fir[u], fir[v])];//得到深度最小的那个点对应的节点编号。
} int main() {
scanf("%d", &t);
while(t--) {
scanf("%d%d", &n, &m);
for(int i = ; i <= n; i++) {
g[i].clear();
dis[i] = ;
}
int u, v, w;
for(int i = ; i < n; i++) {
scanf("%d%d%d", &u, &v, &w);
g[u].push_back(Node(v, w));
g[v].push_back(Node(u, w));
}
cnt = ;
dfs(, , );
RMQ( * n - );
while(m--) {
scanf("%d%d", &u, &v);
int val = lca_query(u, v);
printf("%d\n", dis[u] + dis[v] - * dis[val]);
}
} return ;
}
https://www.cnblogs.com/JVxie/p/4854719.html离线做法tajan。
https://vjudge.net/contest/295298#problem/B 离线lca算法
先把每个点的父节点记为自己,然后dfs遍历,直到回溯的时候才更新该节点的父节点。
对于查询的两点,互相记录对方的编号以及这是第一次查询的编号,然后当其中某个点被遍历到,但另外一个点没有被遍历到,那个继续dfs,如果遍历一个点,他对应的那个点已经遍历过了,那个他们的
最近公共祖先就是之前那个被遍历过的点的父节点,(画个图想想,那个父节点就是他们的最近公共祖先)
离线做法n+q。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 1e5 + ;
typedef pair<int, int> pii;
vector<pii> g[maxn], que[maxn];
int t;
int n, m, k;
int ans[maxn], vis[maxn], pa[maxn], res[maxn], dis[maxn];
//ans数组:每个点的祖先节点编号,pa数组:每个点的父节点的编号。
int fid(int x) {
if(x == pa[x]) return x;
pa[x] = fid(pa[x]);
return pa[x];
} void unio(int x, int y) {
int u = fid(x);
int v = fid(y);
if(u != v) pa[u] = v;
} void lca(int u, int fa) {
ans[u] = u;
for(int i = ; i < g[u].size(); i++) {
int v = g[u][i].first;
if(v == fa) continue;
dis[v] = dis[u] + g[u][i].second;
lca(v, u);
unio(u, v);//连通起来,因为这个是先dfs下去回溯之后的操作,所以这个操作之后只是u的子节点与u组成的图。
ans[fid(u) ] = u;//记录整个子图的的祖先,相当于这个子树节点的祖先就都是u,儿子们得到祖先的时候也要先fid自己父节点一下。
}
vis[u] = ;
for(int i = ; i < que[u].size(); i++) {
int v = que[u][i].first;
int w = que[u][i].second;
if(vis[v]) res[w] = dis[u] + dis[v] - * dis[ans[fid(v)] ];
}
} int main() {
while(~scanf("%d%d", &n, &m)) {
int u, v, w;
char ch[];
for(int i = ; i <= n; i++) {
pa[i] = i;
ans[i] = ;
vis[i] = ;
res[i] = ;
g[i].clear();
que[i].clear();
}
for(int i = ; i <= m; i++) {
scanf("%d%d%d%s", &u, &v, &w, ch);
g[u].push_back(pii(v, w));
g[v].push_back(pii(u, w));
}
scanf("%d", &k);
for(int i = ; i <= k; i++) {
scanf("%d%d", &u, &v);
que[u].push_back(pii(v, i));
que[v].push_back(pii(u, i));
}
dis[] = ;
lca(, );
for(int i = ; i <= k; i++) printf("%d\n", res[i]);
}
return ;
}
题目
给你N个点的无向连通图,图中有M条边,第j条边的长度为: djdj现在有 K个询问。每个询问的格式是:A B,表示询问从A点走到B点的所有路径中,最长的边最小值是多少?
数据范围
50% 1<=N,M<=3000
其中30% K<=5000
100% 1 <= N <= 15,000 1 <= M <= 30,000 1 <= dj<= 1,000,000,000 1 <= K <= 20,000
分析
从题目看,有一句话很重要:表示询问从A点走到B点的所有路径中,最长的边最小值是多少?”所有“这词提醒了我们这些路径中有很多是无用的,是浪费的。我们会发现这题要求的条件也很特殊,也就是只要是连通的路径,就可以取里面的最大值了。再者,既然最大值最小,那么我们可以先使这些选出来的路径尽量的小,且可以使他们连通就可以了,这就变成了一棵树,那么也就是说这n个点中我们可以先取n-1条边,使得这n-1条边可以代表这些其他边,还有要最小,自然而然就是最小生成树了!做完最小生成树后这个图就变成一棵树,然后再处理这棵树每两个点之间的最大边的值,也就是用lca+rmql处理,便可以使复杂度变成Nlogn的。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std; #define N 15000+10
#define M 40000+10
struct Etype{
int u,v,d;
}E[M];
const int MX=; int Node[M],Next[M],D[M],Head[N],tot;
int f[N];
int Fa[N][],V[N][],H[N];
int n,m,Q; bool cmp(Etype a,Etype b) {return a.d < b.d ;} void Swap(int *a,int *b) {int t=*a; *a=*b; *b=t;} void link(int u,int v,int w) {
Node[++tot]=v;
D[tot]=w;
Next[tot]=Head[u];
Head[u]=tot;
} int getfather(int x) {
if(f[x]==x) return x;
return f[x]=getfather(f[x]);
} void DFS(int F,int x,int depth) {
H[x]=depth;
for(int p=Head[x];p;p=Next[p]) {
if(Node[p]==F) continue;
Fa[Node[p]][]=x;
V[Node[p]][]=D[p];
DFS(x,Node[p],depth+);
}
} int Lca(int x,int y) {
int ans=;
if(H[x]<H[y]) swap(x,y);
for(int i=MX;i>=;i--)
if(H[Fa[x][i]]>=H[y]) {
ans=max(ans,V[x][i]);
x=Fa[x][i];
}
//让x,y达到同一个深度,如果倍增后还在另一个点的下面,那么继续倍增,并且更新答案,因为这些点肯定是包含在路径上的
if(x==y) return ans;
for(int i=MX;i>=;i--) {
if(Fa[x][i]!=Fa[y][i]) {
ans=max(ans,V[x][i]);
ans=max(ans,V[y][i]);
x=Fa[x][i];
y=Fa[y][i];
}
}//在到达统一深度之后一起往上走,如果倍增后的点相同就说明这个点是lca的祖先节点或他自己,不能更新答案,只有倍增后的点不相同才能更新答案。
ans=max(ans,V[x][]);
ans=max(ans,V[y][]);
return ans;
} int main()
{
scanf("%d%d%d",&n,&m,&Q);
for(int i=;i<=n;i++) f[i]=i;
for(int i=;i<=m;i++) {
scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].d);
}
sort(E+,E+m+,cmp);
//得到一个最小生成树。
for(int i=;i<=m;i++) {
int f1=getfather(E[i].u);
int f2=getfather(E[i].v);
if(f1!=f2) {
f[f2]=f1;
link(E[i].u,E[i].v,E[i].d);
link(E[i].v,E[i].u,E[i].d);
}
}
DFS(,,);
for(int k=;k<=MX;k++) {
for(int i=;i<=n;i++) {
if(!Fa[Fa[i][k-]][k-]) continue;
Fa[i][k]=Fa[Fa[i][k-]][k-];
V[i][k]=max(V[i][k-],V[Fa[i][k-]][k-]);
}
}
for(int i=;i<=Q;i++) {
int u,v;
scanf("%d%d",&u,&v);
printf("%d\n",Lca(u,v));
}
return ;
}
【JZOJ2753】树(tree)
在这个问题中,给定一个值S和一棵树。在树的每个节点有一个正整数,问有多少条路径的节点总和达到S。路径中节点的深度必须是升序的。假设节点1是根节点,根的深度是0,它的儿子节点的深度为1。路径不必一定从根节点开始。 第一行是两个整数N和S,其中N是树的节点数。 第二行是N个正整数,第i个整数表示节点i的正整数。 接下来的N-1行每行是2个整数x和y,表示y是x的儿子。 输出路径节点总和为S的路径数量。 Sample Input Sample Output Data Constraint Hint 对于30%数据,N≤; 对于60%数据,N≤; 对于100%数据,N≤,所有权值以及S都不超过1000。
正解即为树上倍增+二分
首先还是一样,用logn的时间预处理anc数组
至于权值,你可以顺便维护dis数组,但为何不用简单的前缀和呢?
剩下来的就比较简单啦
枚举节点i ,二分一个mid表示i 的第mid个祖先.
然后通过anc数组用O(log2n)
的时间求出i的第mid个祖先是哪个节点 (设为是第k号)
若pre[i]−pre[k]>s 则right=mid−1 pre[i]−pre[k]<s则left=mid+1
如果 =s就刚好找到了
此时累加答案即可 时间复杂度为O(nlogn * logn)
#include<bits/stdc++.h>
#define MAXN 100001 using namespace std; int last[MAXN],next[MAXN],tov[MAXN];
int a[MAXN],value[MAXN],depth[MAXN];
int f[MAXN][];
int n,s,tot,ans; void insert(int x,int y)
{
next[++tot]=last[x];
last[x]=tot;
tov[tot]=y;
} void dfs(int x)
{
for (int i=last[x];i;i=next[i])
{
int j=tov[i];
value[j]=value[x]+a[j];
f[j][]=x;
depth[j]=depth[x]+;
dfs(j);
}
} int find(int x,int k)
{
int t=;
while (k)
{
if(k&)x=f[x][t];
t++;k/=;
}//类似于快速幂的方法。往上倍增求解。
return x;
} bool judge(int x)
{
int left=,right=depth[x];
while (left<=right)
{
int mid=(left+right)/,temp=find(x,mid);
if (value[x]-value[temp]==s)
{
return ;
}
else
{
if (value[x]-value[temp]>s)
{
right=mid-;
}
else
{
left=mid+;
}
}
}
return ;
} int main()
{
scanf("%d%d",&n,&s);
for (int i=;i<=n;i++)
{
scanf("%d",&a[i]);
}
for (int i=;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
insert(x,y);
}
depth[]=,value[]=a[];
dfs();
for (int j=;j<=floor(log(n)/log());j++)
{
for (int i=;i<=n;i++)
{
f[i][j]=f[f[i][j-]][j-];
}
}
for (int i=;i<=n;i++)
{
if (judge(i))ans++;
}
printf("%d\n",ans);
return ;
}
lca最近公共祖先与树上倍增。的更多相关文章
- LCA(最近公共祖先)之倍增算法
概述 对于有根树T的两个结点u.v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u.v的祖先且x的深度尽可能大. 如图,3和5的最近公共祖先是1,5和2的最近公共祖先是4 在本篇中我们先介 ...
- 求LCA最近公共祖先的在线倍增算法模板_C++
倍增求 LCA 是在线的,而且比 ST 好写多了,理解起来比 ST 和 Tarjan 都容易,于是就自行脑补吧,代码写得容易看懂 关键理解 f[i][j] 表示 i 号节点的第 2j 个父亲,也就是往 ...
- caioj 1237: 【最近公共祖先】树上任意两点的距离 在线倍增ST
caioj 1237: [最近公共祖先]树上任意两点的距离 倍增ST 题目链接:http://caioj.cn/problem.php?id=1237 思路: 针对询问次数多的时候,采取倍增求取LCA ...
- Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载)
Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载) 转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2 ...
- LCA(最近公共祖先)模板
Tarjan版本 /* gyt Live up to every day */ #pragma comment(linker,"/STACK:1024000000,1024000000&qu ...
- CodeVs.1036 商务旅行 ( LCA 最近公共祖先 )
CodeVs.1036 商务旅行 ( LCA 最近公共祖先 ) 题意分析 某首都城市的商人要经常到各城镇去做生意,他们按自己的路线去做,目的是为了更好的节约时间. 假设有N个城镇,首都编号为1,商人从 ...
- LCA 近期公共祖先 小结
LCA 近期公共祖先 小结 以poj 1330为例.对LCA的3种经常使用的算法进行介绍,分别为 1. 离线tarjan 2. 基于倍增法的LCA 3. 基于RMQ的LCA 1. 离线tarjan / ...
- lca 最近公共祖先
http://poj.org/problem?id=1330 #include<cstdio> #include<cstring> #include<algorithm& ...
- LCA近期公共祖先
LCA近期公共祖先 该分析转之:http://kmplayer.iteye.com/blog/604518 1,并查集+dfs 对整个树进行深度优先遍历.并在遍历的过程中不断地把一些眼下可能查询到的而 ...
随机推荐
- 清理 /dev/vda1 系统磁盘
df-h检查一台服务器磁盘使用空间,发现磁盘已经使用了100% 思路是: 1.cd /usr 2.du -sh * 看哪个目录占用空间大 3.重复前两步,根据实际情况删除或者移走 4.日志的话可以运行 ...
- 实现ENSP模拟器与物理主机、虚拟机通信
一.环境描述 我需要实现华为模拟器中的网络设备和物理主机.虚拟机通信.这篇文章中以ENSP中的路由器为例,实现它和物理主机.虚拟机的通信. 二.实现方法 在ENSP中借助Cloud来实现. 在Clo ...
- [Codechef CHSTR] Chef and String - 后缀数组
[Codechef CHSTR] Chef and String Description 每次询问 \(S\) 的子串中,选出 \(k\) 个相同子串的方案有多少种. Solution 本题要求不是很 ...
- checkstyle配置规格说明
参考文献:https://blog.csdn.net/yang1982_0907/article/details/18086693?utm_source=blogxgwz1 https://blog. ...
- ISCC2018_leftleftrightright-Writeup
leftleftrightright(150) 这个题学到了不少东西,值得认真写一下 下载好文件后发现是upx的壳,upx -d直接脱掉后运行,发现是经典的check输入的题目(作为一个linuxer ...
- SpringCloud大白话之服务注册中心
SpringCloud-Eureka白话说明 eureka.client.register-with-eureka 属性表示是否将自己注册到Eureka Server, 默认为true. 由于当前应用 ...
- 两个map合并
两个map合并所用函数为:putAll package myProject; import java.util.HashMap; import java.util.Map; public class ...
- borderInterpolate()函数
官网:borderInterpolate borderInterpolate 函数原型 int borderInterpolate( int p, int len, int borderType ); ...
- MVC5+EF6 入门完整教程2 :从前端UI开始
MVC分离的比较好,开发顺序没有特别要求,先开发哪一部分都可以,这次我们主要讲解前端UI的部分. ASP.NET MVC抛弃了WebForm的一些特有的习惯,例如服务器端控件,ViewState这些东 ...
- 不能暴露服务给外部环境,因为nginx-ingress-control启动失败
不能暴露服务给外部环境,因为nginx-ingress-control启动失败 待办 rancher 和k8s中的端口冲突,nginx-ingress-control都需要使用80端口 以及443端口 ...