前言
第一次用倍增做 LCA L C A
链接
https://blog.csdn.net/xuxiayang/article/details/80171086#ssl-1746
大意
给定一个 n n 个点,条边的五项图,给定 m m 个点,求走完它们所有点的长度
思路
乍看似乎是搜索,然而绝对超时,正解是用去解决,我们把商人的路线看作第二张图, LCA L C A 求的是第一张图的祖先,但计算的时候得用第二张图,在 LCA L C A 的过程中,保存层数就可以计算距离啦!
代码-Tarjan
#include<cstdio>
#include<vector>
#include<cstring>
#define min(a,b) a<b?a:b
#define N 30001
using namespace std;bool ok;
int l[N],n,m,last,tot,root,f[N],w[N],ans[N],d[N],sum;
short vis[N];
struct node{int from,next,to;}edge[N<<1];//邻接表
void add(int u,int v){edge[++tot].from=u;edge[tot].to=v;edge[tot].next=l[u];l[u]=tot;}//邻接表
int find(int x){return x==f[x]?x:f[x]=find(f[x]);}//并查集
vector<int>num[N],id[N];//保存相同点连接的路,相当于一个另类邻接表,时间复杂度和邻接表相同,id是保存它出现的顺序
int read()//输入流
{
char c;int d=1,f=0;
while((c=getchar())<48||c>57)if(c=='-')d=-1;f=(f<<3)+(f<<1)+c-48;
while((c=getchar())>=48&&c<=57) f=(f<<3)+(f<<1)+c-48;
return d*f;
}
void write(int x)//输出流
{
if(x>9) write(x/10);putchar(x%10+48);return;
}
void LRZ()//输入
{
n=read();f[n]=n;int x,y;
for(int i=1;i<n;i++)
{
x=read();y=read();f[i]=i;//记得给并查集初始化
add(x,y);add(y,x);//连边
}
return;
}
void dfs(int k)
{
vis[k]=true;//标记到达过
for(int i=l[k];i;i=edge[i].next)
{
int y=edge[i].to;
if(vis[y]) continue;//搜过了就直接返回
d[y]=d[k]+1;//每次往儿子递归,层次要更深
dfs(y);//搜
f[find(edge[i].to)]=edge[i].from;//连接
}
for(int i=0;i<num[k].size();i++)
{
int y=num[k][i],Id=id[k][i];//取出点和坐标
if(vis[y]==2)
{
int lca=find(y);//最近公共祖先
ans[Id]=min(ans[Id],d[k]+d[y]-(d[lca]<<1));//计算它们的距离
}
}
vis[k]=2;//标记找过
return;
}
void add(int x,int y,int k)//建造第二张图
{
num[x].push_back(y);id[x].push_back(k);
num[y].push_back(x);id[y].push_back(k);
}
int main()
{
LRZ();
m=read();last=1;int x,y;//last初始化为起点
for(int i=1;i<=m;i++)
{
x=read();
if(last==x) continue;//相等不管它
ans[i]=2147483647;
add(x,last,i);
add(last,x,i);//建造第二张图
last=x;//赋值
}
dfs(1);//Tarjan
for(int i=1;i<=m;i++) sum+=ans[i];//求和
write(sum);//输出
}
代码-倍增
#include<cstdio>
#include<cmath>
#define swap(a,b) {a^=b;b=a^b;a^=b;}//交换
#define N 30001
using namespace std;
int n,m,maxn,d[N],l[N],fa[N],tot,f[N][17],last,ans,now;
struct node{int from,to,next;}edge[N<<1];//邻接表
void add(int u,int v)//建边
{
edge[++tot].from=u;
edge[tot].to=v;
edge[tot].next=l[u];
l[u]=tot;
return;
}
int read()//输入流
{
char c;int d=1,f=0;
while((c=getchar())<48||c>57)if(c=='-')d=-1;f=(f<<3)+(f<<1)+c-48;
while((c=getchar())>=48&&c<=57) f=(f<<3)+(f<<1)+c-48;
return d*f;
}
void write(int x)//输出流
{
if(x>9) write(x/10);putchar(x%10+48);return;
}
void LRZ()//输入
{
n=read();int x,y;
for(int i=1;i<n;i++)
{
x=read();y=read();
add(x,y);add(y,x);//建边
}
return;
}
void dfs(int k,int dep)
{
if(dep>maxn) maxn=dep;//保存最深层
d[k]=dep;//保存层
for(int i=l[k];i;i=edge[i].next)
if(edge[i].to!=fa[edge[i].from])//终点起点不相同
{
fa[edge[i].to]=edge[i].from;//并查集(注意没有路径压缩)
dfs(edge[i].to,dep+1);//继续搜
}
return;
}
void bz()//倍增
{
dfs(1,1);//先搜
f[1][0]=-1;//初始化
for(int i=2;i<=n;i++) f[i][0]=fa[i];//初始化
for(int j=1;j<=floor(log(maxn)/log(2));j++)
for(int i=1;i<=n;i++)
if(f[i][j-1]>-1) f[i][j]=f[f[i][j-1]][j-1];else f[i][j]=-1;//根据爸爸的爸爸是爷爷推出此方程
return;
}
int lca(int x,int y)//求x,y的最近公共祖先
{
if(d[x]<d[y]) swap(x,y);//保证解的唯一性,不重复计算
for(int i=16;i>=0;i--) if(d[x]-(1<<i)>=d[y]) x=f[x][i];//修改
for(int i=16;i>=0;i--)
if(f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];//往上爬
}
if(x==y) return x;//若到达它们的祖宗,返回
return fa[x];//返回x的祖宗,因为x一定比y大(前面已经交换过)
}
int main()
{
LRZ();//输入
bz();//倍增
m=read();last=read();int x,y;//输入
for(int i=2;i<=m;i++)
{
x=last;y=read();last=y;//保存上一次
now=lca(x,y);//计算位置
ans+=d[x]+d[y]-(d[now]<<1);//求出每次的和
}
write(ans);//输出
}