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最近公共祖先与树上倍增。的更多相关文章

  1. LCA(最近公共祖先)之倍增算法

    概述 对于有根树T的两个结点u.v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u.v的祖先且x的深度尽可能大. 如图,3和5的最近公共祖先是1,5和2的最近公共祖先是4 在本篇中我们先介 ...

  2. 求LCA最近公共祖先的在线倍增算法模板_C++

    倍增求 LCA 是在线的,而且比 ST 好写多了,理解起来比 ST 和 Tarjan 都容易,于是就自行脑补吧,代码写得容易看懂 关键理解 f[i][j] 表示 i 号节点的第 2j 个父亲,也就是往 ...

  3. caioj 1237: 【最近公共祖先】树上任意两点的距离 在线倍增ST

    caioj 1237: [最近公共祖先]树上任意两点的距离 倍增ST 题目链接:http://caioj.cn/problem.php?id=1237 思路: 针对询问次数多的时候,采取倍增求取LCA ...

  4. Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载)

    Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载) 转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2 ...

  5. LCA(最近公共祖先)模板

    Tarjan版本 /* gyt Live up to every day */ #pragma comment(linker,"/STACK:1024000000,1024000000&qu ...

  6. CodeVs.1036 商务旅行 ( LCA 最近公共祖先 )

    CodeVs.1036 商务旅行 ( LCA 最近公共祖先 ) 题意分析 某首都城市的商人要经常到各城镇去做生意,他们按自己的路线去做,目的是为了更好的节约时间. 假设有N个城镇,首都编号为1,商人从 ...

  7. LCA 近期公共祖先 小结

    LCA 近期公共祖先 小结 以poj 1330为例.对LCA的3种经常使用的算法进行介绍,分别为 1. 离线tarjan 2. 基于倍增法的LCA 3. 基于RMQ的LCA 1. 离线tarjan / ...

  8. lca 最近公共祖先

    http://poj.org/problem?id=1330 #include<cstdio> #include<cstring> #include<algorithm& ...

  9. LCA近期公共祖先

    LCA近期公共祖先 该分析转之:http://kmplayer.iteye.com/blog/604518 1,并查集+dfs 对整个树进行深度优先遍历.并在遍历的过程中不断地把一些眼下可能查询到的而 ...

随机推荐

  1. Oracle Data Guard搭建 1.虚拟机安装linux

    1.安装虚拟机 VMware 14 2.下载Linux镜像文件,创建虚拟机

  2. python,装饰器带参数,原理

    import time # 函数装饰器传参 def zhuang1(hao): def zhuang2(func1): def funczhuang(*args, **kw): print(time. ...

  3. OpenGL基本图元类型

    GL_POINTSGL_LINESGL_LINE_STRIPGL_LINE_LOOPGL_TRIANGLESGL_TRIANGLE_STRIPGL_TRIANGLE_FANGL_QUADSGL_QUA ...

  4. SpringCloud Netflix Hystrix

    Hystrix的一些概念 Hystrix是一个容错框架,可以有效停止服务依赖出故障造成的级联故障. 和eureka.ribbon.feign一样,也是Netflix家的开源框架,已被SpringClo ...

  5. 【算法学习记录-排序题】【PAT A1062】Talent and Virtue

    About 900 years ago, a Chinese philosopher Sima Guang wrote a history book in which he talked about ...

  6. 使用imread()函数读取图片的六种正确姿势

    OpenCV实践之路——使用imread()函数读取图片的六种正确姿势 opencv里的argv[1]指向的文件在哪里 测试 #include "opencv2/highgui/highgu ...

  7. 查看gcc编译器版本

    我们在windows下DS5中编译时使用GCC交叉编译器,但是在ubuntu时也需要使用GCC编译器,这时最好时保持版本一致,所以就需要查看windows下版本,如下图,在按装的文件夹中找到对应得文件 ...

  8. sql server下载教程

    进入官网:https://www.microsoft.com/zh-cn/download/details.aspx?id=29066 点击下载即可: 安装教程:可去csdn下载: win10系统下安 ...

  9. 抽象语法树 Abstract syntax tree

    什么是抽象语法树? 在计算机科学中,抽象语法和抽象语法树其实是源代码的抽象语法结构的树状表现形式 在线编辑器 我们常用的浏览器就是通过将js代码转化为抽象语法树来进行下一步的分析等其他操作.所以将js ...

  10. PAT-链表-A1032 Sharing

    题意:给出两条链表的首地址以及若干个节点的的地址.数据.下一个节点的地址,求两条链表的首个共用节点的地址.如果两条链表没有共用节点,则输出-1. 思路:使用静态链表,首先遍历一遍第一个链表并进行标记. ...