HYSBZ - 3732 最小生成树+倍增Lca

题目:http://www.lydsy.com/JudgeOnline/problem.php?id=3732

码了一个星期终于过了这一题。(渣是原罪...)
最小生成树+倍增lca 
思路不难,但这是我第一次写lca的题目。下面一步步来解释这题。
题意:给你m条无向边,有k个查询,问从a到b所有路径上的最长边的最小值。
首先,从a到b所有路径上的最长边的最小值所对应的边一定在最小生成树上。
为什么?
现在来回忆一下Kru的做法。把所有边从小到大排序,接着从小到大使用这些边去联通每个点,当使用一条边x时,若x的两个端点在此之前已经联通,则说明这两个点在此之前已经被更短的边联通。可以想象,最小生成树上每两个点都是被最短的边联通的。即当两个点联通时的最长边不可能更小。
所以,现在问题转化为从a到b的在最小生成树上的路径的最长边是多少。
可知从a到b的最长边等于max(a->最近公共祖先c的最长边,b->c的最长边),即题目转化为lca问题。
至此,思路就很明确了。先构建最小生成树,再寻找两个点的最近公共祖先。
本蒟蒻用倍增lca寻找最近公共祖先,关于倍增lca的解释可以看以下博客( https://riteme.github.io/blog/2016-2-1/lca.html)。
我们设mx[i][j]表示点i到fa[i][j]的最长边,与fa[i][j]=fa[fa[i][j-1]][j-1]相似mx[i][j]=max(mx[i][j-1],mx[fa[i][j-1]][j-1]),即点i到fa[i][j]的最长边等于点i到fa[i][j-1]的最长边与点u(fa[i][j-1])到点fa[u][j-1]的最长边的最大值。(如图)


代码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <string>
#include <queue>
#include <map>
#include <vector>
#include <bitset>
#include <set>

using namespace std;

typedef long long LL;
typedef pair<int,int> pii;
const int maxn=3e4+20;
const int maxm=2700+10;
const int maxb=25;
const int mod=1e9+7;
const int inf=1e9+7;
const double infD=1000000007.0;
const double eps=0.00000001;

typedef struct Edge
{
    int x,y,w;
    Edge() {}
    Edge(int _x,int _y,int _w)
    {
        x=_x;
        y=_y;
        w=_w;
    }
    bool operator <(const struct Edge &r)const
    {
        return w<r.w;
    }
} Edge;
typedef struct Node
{
    int v,w;
    Node() {}
    Node(int _v,int _w)
    {
        v=_v;
        w=_w;
    }
} Node;
int n,m,Q;
int p[maxn];
Edge e[maxn];
vector<Node> G[maxn];
bool vis[maxn];
int deep[maxn],fa[maxn][maxb],mx[maxn][maxb];

int Find(int x)
{
    if(p[x]==x) return x;
    else return p[x]=Find(p[x]);
}

void Kru()
{
    for(int i=1; i<=n; i++) p[i]=i,G[i].clear();
    sort(e,e+m);
    int cnt=0;
    for(int i=0; i<m; i++)
    {
        if(cnt==n-1) break;
        int x=Find(e[i].x),y=Find(e[i].y);
        if(x==y) continue;
        cnt++;
        p[y]=x;
        G[e[i].x].push_back(Node(e[i].y,e[i].w));
        G[e[i].y].push_back(Node(e[i].x,e[i].w));
    }
}

void dfs(int u,int d)
{
    vis[u]=1;
    deep[u]=d;
    int len=G[u].size();
    for(int i=0; i<len; i++)
    {
        int v=G[u][i].v;
        if(!vis[v])
        {
            fa[v][0]=u;
            mx[v][0]=G[u][i].w;
            dfs(v,d+1);
        }
    }
}

void init()
{
    for(int j=1; j<maxb; j++)
    {
        for(int i=1; i<=n; i++)
        {
            int x=i;
            if(~fa[x][j-1])
            {
                fa[x][j]=fa[fa[x][j-1]][j-1];
                mx[x][j]=max(mx[x][j-1],mx[fa[x][j-1]][j-1]);
            }
        }
    }
}

int lca(int u,int v)
{
    int ans=0;
    if(deep[u]<deep[v]) swap(u,v);
    for(int i=maxb-1; i>=0; i--)
    {
        if(deep[fa[u][i]]>=deep[v])
        {
            ans=max(ans,mx[u][i]);
            u=fa[u][i];
        }
    }
    if(u==v) return ans;
    for(int i=maxb-1; i>=0; i--)
    {
        if(fa[u][i]!=fa[v][i])
        {
            ans=max(ans,mx[u][i]);
            ans=max(ans,mx[v][i]);
            u=fa[u][i];
            v=fa[v][i];
        }
    }
    ans=max(ans,mx[u][0]);
    ans=max(ans,mx[v][0]);
    return ans;
}

void work()
{
    for(int i=0; i<m; i++)
        scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].w);
    Kru();

    memset(vis,0,sizeof(vis));
    memset(fa,0xff,sizeof(fa));
    memset(mx,0,sizeof(mx));
    memset(deep,0,sizeof(deep));
    dfs(1,1);
    init();

    while(Q--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        printf("%d\n",lca(a,b));
    }
}

int main()
{
#ifdef LOCAL
    freopen("input.in","r",stdin);
#endif // LOCAL
//    while(~scanf("%d%d%d",&n,&m,&Q))
//        work();
    scanf("%d%d%d",&n,&m,&Q);
    work();
    return 0;
}

后记:

这道题听师兄说是可以直接构造出最小生成树,构造的时候和并查集一样,按秩合并,就能保证树高是logn的,就不用写倍增lca了。但是本蒟蒻实在是太渣了,写了很久都还是TLE,有兴趣的大佬可以试一下,随便教下弱弱的我。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值