【LCA】SSL-1746 商务旅行

前言

第一次用倍增做 LCA L C A

链接

https://blog.csdn.net/xuxiayang/article/details/80171086#ssl-1746

大意

给定一个 n n 个点,n1条边的五项图,给定 m m 个点,求走完它们所有点的长度

思路

乍看似乎是搜索,然而绝对超时,正解是用LCA去解决,我们把商人的路线看作第二张图, 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);//输出
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值