qwb与学姐 【MST】+【倍增求lca 同时维护距离】

Problem H: qwb与学姐
Time Limit: 1 Sec Memory Limit: 128 MB
Submit: 113 Solved: 44
[Submit][Status][Web Board]
Description
qwb打算向学姐表白,可是学姐已经受够了他的骚扰,于是出了一个题想难住他:
已知一幅n个点m条边的无向图,定义路径的值为这条路径上最短的边的长度,
现在有 k个询问,
询问从A点到B点的所有路径的值的最大值。
qwb听完这个问题很绝望啊,聪明的你能帮帮他吗?
Input
一组数据。
第一行三个整数n,m,k (1<=N<=50000,m<=200000,k<=100000)。
第2..m+1行:三个正整数:X, Y, and D (1 <= X <=N; 1 <= Y <= N,1<=D<=215) 表示X与Y之间有一条长度为D的边。
第m+2..m+k+1行: 每行两个整数A B(1<=A,B<=n且A≠B),意义如题目描述。
保证图连通。
Output
对于每个询问输出一行,一共k行,每行输出A点到B点的所有路径的值的最大值。
Sample Input
4 5 3
1 2 6
1 3 8
2 3 4
2 4 5
3 4 7
2 3
1 4
3 4
Sample Output
6
7
7
由题意,想要求两点之间的所有路径边权最小 的最大值,所以可以贪心一下,求一个最大生成树。这样的话,因为是树,所以每两点之间的路径都是唯一确定的。每两点之间的所有边都是尽可能大的,找到这里面的最小值就是我们要的。

然后就是 k次询问 ,。可以用到lca 来求,我还只会 lca转rmq,和最朴素的步进法求lca。 RMQ 的还不会维护过程量【比如距离】,步进法还TLE。看了题解,才知道可以用倍增法求(同时维护距离还是很方便)

辨析一下吧,lca转RMQ 和倍增法其实都还好,但是rmq 模板那真心容易挂,debug真累,还是用倍增法好,写起来方便。

就学习下吧。下面例题。
倍增求lca 同时维护距离 求最短路
上面的例子要好好看,因为只是求lca 很简单,有多中算法 ,都可以用,但是可以用来维护过程【比如最大最小距离】的,倍增法还是很好用,同时时间复杂度也不高。

看懂了上面的例题,在看这个就很清晰了;
代码

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define LL long long
const int MAXN = 50000+100;
const int MAXM = 1e6+100;
const double PI = acos(-1.0);
const double eps = 1e-8;
inline int read(){
    int x=0,f=1; char ch=getchar();
    while (ch<'0'||ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
    while (ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); }
    return x*f;
}
/*--------------------------------------*/
struct Edge{
    int from,to,val,next;
}edge1[MAXN<<2],edge[MAXN<<2];

int head[MAXN],top;
int n,m,k;

int cmp(Edge a,Edge b)
{
    return a.val>b.val;
}
void init()
{
    memset(head,-1,sizeof(head));
    top=0;
}
void addedge(int a,int b,int c)
{
    Edge e={a,b,c,head[a]};
    edge[top]=e;head[a]=top++;
}

void getmap()
{
    int a,b,c;
    for(int i=1;i<=m;i++)
    scanf("%d%d%d",&edge1[i].from,&edge1[i].to,&edge1[i].val);
} 

/*----------------------------*/最大生成树 库斯卡尔算法
int pre[MAXN];
int find(int x)
{
    int r=x;
    if(r==pre[x])  return x;
    while(r!=pre[r]) r=pre[r];
    int j=x;
    while(j!=r)
    {
        int k=pre[j];
        pre[j]=r;
        j=k;
    }
    return r;
 } 
void join(int x,int y)
{
    x=find(x);
    y=find(y);
    if(x!=y) pre[x]=y;
}
void MST()
{
    sort(edge1+1,edge1+m+1,cmp);
    for(int i=1;i<=m;i++)
    {
        Edge e=edge1[i];
        if(find(e.from)!=find(e.to))
        {
            join(e.from ,e.to); //将最大生成树 构成新图;
            addedge(e.from,e.to,e.val);
            addedge(e.to,e.from,e.val);
         } 
    }
}

/*------------------------*/// 倍增法求lca
int fa[MAXN][20]; // fa[x][i] 代表点x向上跳2^i个节点到达的节点
int depth[MAXN]; //深度
int mi[MAXN][20];//mi[i][j] 代表点x向上跳2^i个节点过程中最小的边权
void dfs(int now,int p,int de)
{
     fa[now][0]=p;
     depth[now]=de;
     for(int i=1;i<20;i++) 
     {
        fa[now][i]=fa[fa[now][i-1]][i-1];//now的2^i祖先是它2^i-1祖先的2^i-1祖先
        mi[now][i]=min(mi[now][i-1],mi[fa[now][i-1]][i-1]);//;//now到它2^i祖先的距离 = min(now到它2^i-1祖先的距离 ,now的2^i-1祖先到它2^i-1祖先距离)
     }

    for(int i=head[now];i!=-1;i=edge[i].next)
    {
        Edge e=edge[i];
        if(e.to!=p)
        {
            mi[e.to][0]=e.val;//赋值
            dfs(e.to,now,de+1);
        }
    }
}

int lca(int x,int y)
{
    if(depth[x]<depth[y]) swap(x,y);//保持x是最深的节点
    int lca=-1;int xx=x;int yy=y;
    for(int i=19;i>=0;i--) 
    if(depth[fa[x][i]]>=depth[y])
    x=fa[x][i]; //将x向上跳到与y相同深度

    if(x==y) lca=y;// 如果能够正好跳到,说明y是最初x的祖先节点
    else
    {
        for(int i=19;i>=0;i--)
        {
            if(fa[x][i]!=fa[y][i])
            {
                x=fa[x][i];
                y=fa[y][i];
             }
          } 
          lca=fa[y][0];
     }

     int mix=inf;int miy=inf;//找到从xx到lca过程中的最小边权,和yy到lca的最小边权,取两者最小就行了
     for(int i=19;i>=0;i--)
     {
        if(depth[fa[xx][i]]>=depth[lca]) 
         { 
            mix=min(mix,mi[xx][i]);
             xx=fa[xx][i];
          } 
        if(depth[fa[yy][i]]>=depth[lca]) 
        {
            miy=min(miy,mi[yy][i]);
            yy=fa[yy][i];   
        }
     }
     return min(mix,miy);
}
void solve()
{
    for(int i=0;i<n+10;i++) //初始化 根节点 
    pre[i]=i;
    MST();
    memset(mi,0x3f,sizeof(mi));//初始化mi数组
    dfs(1,0,0);           
    int le,ri;
    while(k--)
    {
        scanf("%d%d",&le,&ri);
        printf("%d\n",lca(le,ri));
    }
}

int main()
{
    scanf("%d%d%d",&n,&m,&k); 
    init();
    getmap();
    solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值