数据结构专题 - 解题报告 - B

这篇博客分享了作者在解决一道数据结构问题时的经验,涉及最大生成树确保带宽最大化和使用倍增LCA算法优化搜索过程。通过预处理和DFS遍历,构建有向图并寻找相邻点的关系,最终实现高效求解路径信息。

这是我这个专题做出来的第一道题,跟榜看到的B,读完题,惊了惊了!似李!货车运输!当时也不说忙着抢一血,反正就是把自己写过的代码直接粘来改改交了过了。
但这样做的意义仅仅是多一个过题数而已,还是得好好整理一下。

分析完题意后,我们知道我们需要一个最大生成树保证带宽尽可能大(因为我们不会去选择更低带宽的路),然后在一条路上找到带宽最小值。就可以搜图并记录边权最小值即可。但是这么暴力去搜基本上是必T的。我们就需要运用倍增lca来解决问题。

什么是倍增?个人理解这是一种预处理的方式,通过处理(一般以2为底数),得到区间部分的状态,在全图运作时不用每个单位每个单位计算。
比如在一张图上走7格,一步一步走需要7次运算,而如果通过这样的预处理,提前知道了22 ,21 ,20 的状态,那么只用3步就可以累加完成(4 + 2 + 1)。数字越大优化效果越明显。


那我们就来预处理吧:

FOR(i, 1, 20)
        FOR(j, 1, n)
        {
            f[j][i] = f[f[j][i-1]][i-1];				//递推
            w[j][i] = min(w[j][i-1], w[f[j][i-1]][i-1]);
        }

在dfs的过程中已经有f[to][0] = now即上一节点(处理见代码),f[to][1]就是表示倒数第二个节点位置,那么f[to][2]呢?f[to][2] = f[f[to][1]][1],就是f[to][1]的倒数前两个节点,累计起来就是当前节点的前第4(22)个节点,以此类推我们能完成区间节点之间长度处理。同步更新路径上带宽最小值( w[ ][ ] ),预处理就算完成了。


那么整体思路就很清晰了,找最大生成树,然后用树枝建有向图,dfs走一遍找出相邻点间关系,运用相邻点关系开始倍增预处理,因为树形结构没有环,所以单向走图搜索可以优化为双向找到公共祖先节点(LCA,而lca通常就是需要倍增来优化),这个过程结束后就能更新出整条路径的信息。

至于lca,它本身就是两个高深度的点向低深度的点回溯,必然会相遇在同一个祖先节点的过程,但是与倍增的结合后的细节操作我就放在AC代码里讲吧

#include<bits/stdc++.h>
#define maxn 200005
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define hrdg 1000000007
#define inf 0x7fffffff
#define ll long long
#define pi acos(-1.0)			//一看我这些全局定义就是上个时代的版本了
using namespace std;

int n, m, u, v, d, q;
int deep[maxn], f[maxn][21], fa[maxn], w[maxn][21];
int vis[maxn];

struct node1
{
    int x, y, dis;
}e[maxn*5];
struct node2
{
    int to, nex, dis;
}edge[maxn*5];
int head[maxn], tot;

void add(int u, int v, int d)   //前向星建立邻接表
{
    tot++;
    edge[tot].to = v;
    edge[tot].nex = head[u];
    edge[tot].dis = d;
    head[u] = tot;
}

//快读
inline int read()
{
    char c=getchar();long long x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

//并查集
inline int find(int x)
{
    return x == fa[x] ? x : fa[x]=find(fa[x]);
}
inline void ad(int x,int y)//并
{
    x=find(fa[x]);
    y=find(fa[y]);
    fa[x]=y;
}
inline bool check(int x,int y)//查
{
    x=find(x);
    y=find(y);
    if(x==y) return true;
    return false;
}

bool cmp(node1 x, node1 y)
{
    return x.dis > y.dis;     //最大生成树,从大到小排序
}

void kruskal()
{
    sort(e+1, e+1+m, cmp);
    FOR(i, 1, n) fa[i] = i;     //并查集初始化
    FOR(i, 1, m)
    {
        if(!check(e[i].x, e[i].y))
        {
            ad(e[i].x, e[i].y);
            add(e[i].x, e[i].y, e[i].dis);      //当可以建立生成树时,建立邻接表
            add(e[i].y, e[i].x, e[i].dis);
        }
    }
}

void dfs(int now)
{
    vis[now] = true;
    for(int i=head[now]; i; i=edge[i].nex)
    {
        int to = edge[i].to;
        if(vis[to]) continue;
        deep[to] = deep[now] + 1;
        f[to][0] = now;
        w[to][0] = edge[i].dis;
        dfs(to);
    }
}

int lca(int x, int y)
{
    if(!check(x, y)) return -1;     //不连通
    int ans = inf;
    if(deep[x] > deep[y]) swap(x, y);       //保证y更深
    //将y提到和x相同的高度
    for(int i=20; i>=0; i--)
    if(deep[f[y][i]] >= deep[x])
    {
        ans = min(ans, w[y][i]);    //一直更新最大载重
        y = f[y][i];    //y向上跳
    }
    if(x==y) return ans;    //如果已经相等
    for(int i=20; i>=0; i--)
        if(f[x][i] != f[y][i])
        {
            ans = min(ans, min(w[x][i], w[y][i]));      //更新
            x = f[x][i];    //同时向上跳
            y = f[y][i];
        }
        ans = min(ans, min(w[x][0], w[y][0]));      //最后再比较两个点的带宽要求
        return ans;
}

int main()
{
    n = read();
    q = read();
    m = n-1;
    FOR(i, 1, m)
    {
        u=read(); v=read(); d=read();
        e[i].x = u;
        e[i].y = v;
        e[i].dis = d;
    }
    kruskal();      //最大生成树

    FOR(i, 1, n)        //从1开始深搜,序列靠前的点作树根
        if(!vis[i])
        {
            deep[i] = 1;
            dfs(i);
            f[i][0] = i;
            w[i][0] = inf;
        }

    //倍增初始化
    FOR(i, 1, 20)
        FOR(j, 1, n)
        {
            f[j][i] = f[f[j][i-1]][i-1];
            w[j][i] = min(w[j][i-1], w[f[j][i-1]][i-1]);
        }
    FOR(i, 1, q)
    {
        u = read(); v = read();
        printf("%d\n", lca(u, v));
    }
    return 0;
}

/*
6 5
1 2 7
2 3 10
2 4 8
4 6 9
4 5 11
1 6
2 4
3 5
1 3
6 5
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值