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 对整个树进行深度优先遍历.并在遍历的过程中不断地把一些眼下可能查询到的而 ...
随机推荐
- hibernate跟Mybatis/ ibatis 的区别,为什么选择?(转)
第一章 Hibernate与MyBatisHibernate 是当前最流行的O/R mapping框架,它出身于sf.NET,现在已经成为Jboss的一部分. Mybatis 是另外一种优秀的O/R ...
- mysql的优化总结
1,允许数据库适当冗余提高查询效率 2,对于索引的使用要适当
- 关于gets读入因为缓冲区出现的问题
今天被一个同学丢了代码求debug 然后发现bug挺有意思的,稍微记录一下 首先我们读入的东西都会被丢进缓冲区等待接收,比如abc\n,如果你使用scanf读入的话,它在读入到\n的时候就会提取它需要 ...
- [HNOI2014] 道路堵塞 - 最短路,线段树
对不起对不起,辣鸡蒟蒻又来用核弹打蚊子了 完全ignore了题目给出的最短路,手工搞出一个最短路,发现对答案没什么影响 所以干脆转化为经典问题:每次询问删掉一条边后的最短路 如果删掉的是非最短路边,那 ...
- python3练习100题——016
今天的题目比较容易了,旨在让人掌握datetime模块下的一些用法. 链接:http://www.runoob.com/python/python-exercise-example16.html 题目 ...
- linux环境jacoco接入
我们通常会将测试覆盖率分为两个部分,即“需求覆盖率”和“代码覆盖率”. 需求覆盖:指的是测试人员对需求的了解程度,根据需求的可测试性来拆分成各个子需求点,来编写相应的测试用例,最终建立一个需求和用例的 ...
- java 数字转换格式化
1.小数格式化后转字符串百分数(带%)输出 NumberFormat nt = NumberFormat.getPercentInstance(); //设置百分数精确度2即保留两位小数 nt.set ...
- 第三十六篇 入门机器学习——Jupyter Notebook中的魔法命令
No.1.魔法命令的基本形式是:%命令 No.2.运行脚本文件的命令:%run %run 脚本文件的地址 %run C:\Users\Jie\Desktop\hello.py # 脚本一旦 ...
- 防止不同账号之间localStorage数据错误
set和get的时候,key后面加上用户ID
- crontab实践
1.crontab概要 2.crontab使用 3.关键配置信息 3.1如何配置定时任务 4.注意事项 参考 https://www.cnblogs.com/keithtt/p/6946498.htm ...