UVa 11354 Bond (MST 树链剖分 LCA/RMQ)

本文介绍了一种求解任意两点间最小瓶颈路的方法。利用树链剖分和RMQ技术来高效解决该问题,并提供了两种不同的实现思路及其代码示例。

题目:
题意:

有n座城市通过m条双向道路相连,每条道路都有一个危险系数。你的任务是回答若干个询问,每个询问包含一个起点s和一个终点t,要求找到一条从s到t的路,使得途经所有边的最大危险系数最小。
输入最多包含5组数据。(2 <= n <= 50000 , 1 <= m <= 100000 )。询问有Q个(1 <= Q <= 50000 )。起点编号1~n。

分析:

就是求任意两点的最小瓶颈路
那么肯定就是先求MST了
如果n的范围小一点,那么O(n^2)的时间复杂度可以预处理mincost[u][v],但是这题显然会超时
我的做法是树链剖分,然后线段树求区间最值
书上给的做法是用RMQ维护LCA,然后在树上用RMQ维护最值

代码:
树链剖分:

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <string.h>
using namespace std;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
const int N = 50010;
struct EDGE {
    int v,nex;
} edge[N<<1];
int head[N],tot;
int dep[N],p[N],fa[N],top[N],son[N],siz[N];
int cnt,maxn[N<<2];
void addedge(int a,int b ) {
    edge[tot].v=b;
    edge[tot].nex=head[a];
    head[a]=tot++;
}
void dfs(int u) {
    siz[u]=1,son[u]=0;
    for(int i=head[u]; ~i; i=edge[i].nex) {
        int v=edge[i].v;
        if(v!=fa[u]) {
            fa[v]=u;
            dep[v]=dep[u]+1;
            dfs(v);
            if(siz[v]>siz[son[u]])son[u]=v;
            siz[u]+=siz[v];
        }
    }
}
void build(int u,int tp) {
    p[u]=++cnt;
    top[u]=tp;
    if(son[u])build(son[u],tp);
    for(int i=head[u]; ~i; i=edge[i].nex) {
        int v=edge[i].v;
        if(v!=son[u]&&v!=fa[u])build(v,v);
    }
}
void update(int p,int x,int l,int r,int rt) {
    if(l==r) {
        maxn[rt]=x;
        return;
    }
    int m=l+r>>1;
    if(p<=m)update(p,x,lson);
    else update(p,x,rson);
    maxn[rt]=max(maxn[rt<<1],maxn[rt<<1|1]);
}
int query(int a,int b,int l,int r,int rt) {
    if(a<=l&&r<=b)return maxn[rt];
    int m=l+r>>1;
    int ret=0;
    if(a<=m)ret=max(ret,query(a,b,lson));
    if(b>m)ret=max(ret,query(a,b,rson));
    return ret;
}
int find(int a,int b) {
    int f1=top[a],f2=top[b],tmp=0;
    while(f1!=f2) {
        if(dep[f1]<dep[f2])swap(f1,f2),swap(a,b);
        tmp=max(tmp,query(p[f1],p[a],1,cnt,1));
        a=fa[f1],f1=top[a];
    }
    if(a==b)return tmp;
    if(dep[a]>dep[b])swap(a,b);
    return max(tmp,query(p[son[a]],p[b],1,cnt,1));
}

struct Edge {
    int x, y, w;
    bool operator < (const Edge& rhs) const {
        return w < rhs.w;
    }
};

const int maxm = 100000;
Edge e[maxm];
int pa[N],d[N][3];
int findset(int x) {
    return pa[x] != x ? pa[x] = findset(pa[x]) : x;
}
int main() {
    int kase = 0, n, m, x, y, w, Q;
    //freopen("f.txt","r",stdin);
    while(scanf("%d%d", &n, &m) == 2 && n) {

        for(int i = 0; i < m; i++) {
            scanf("%d%d%d", &x, &y, &w);
            e[i] = (Edge) {
                x, y, w
            };
        }
        sort(e, e+m);
        for(int i = 0; i <= n; i++) {
            pa[i] = i;
        }
        tot=cnt=0;
        memset(head,-1,sizeof(head));
        for(int i = 0; i < m; i++) {
            int x = e[i].x, y = e[i].y, w = e[i].w, u = findset(x), v = findset(y);
            if(u != v) {
                pa[u] = v;
                addedge(x,y);
                addedge(y,x);
                ++cnt;
                d[cnt][0]=x;
                d[cnt][1]=y;
                d[cnt][2]=w;
            }
        }
        cnt=tot=0;
        fa[1]=dep[1]=0;
        memset(siz,0,sizeof(siz));
        memset(maxn,0,sizeof(maxn));
        dfs(1);
        build(1,1);
        for(int i=1; i<n; i++) {
            if(dep[d[i][0]]>dep[d[i][1]])swap(d[i][0],d[i][1]);
            update(p[d[i][1]],d[i][2],1,cnt,1);
        }

        if(++kase != 1) printf("\n");
        scanf("%d", &Q);
        while(Q--) {
            scanf("%d%d", &x, &y);
            printf("%d\n",find(x,y));
        }
    }
    return 0;
}

RMQ和LCA:

// UVa11354 Bond
// Rujia Liu
#include<cstdio>
#include<algorithm>
using namespace std;

const int maxn = 100000 + 10;
const int logmaxn = 20;
const int INF = 1000000000;

struct LCA {
    int n;
    int fa[maxn];   // 父亲数组
    int cost[maxn]; // 和父亲的费用
    int L[maxn];    // 层次(根节点层次为0)
    int anc[maxn][logmaxn];     // anc[p][i]是结点p的第2^i级父亲。anc[i][0] = fa[i]
    int maxcost[maxn][logmaxn]; // maxcost[p][i]是i和anc[p][i]的路径上的最大费用

    // 预处理,根据fa和cost数组求出anc和maxcost数组
    void preprocess() {
        for(int i = 0; i < n; i++) {
            anc[i][0] = fa[i];
            maxcost[i][0] = cost[i];
            for(int j = 1; (1 << j) < n; j++) anc[i][j] = -1;
        }
        for(int j = 1; (1 << j) < n; j++)
            for(int i = 0; i < n; i++)
                if(anc[i][j-1] != -1) {
                    int a = anc[i][j-1];
                    anc[i][j] = anc[a][j-1];
                    maxcost[i][j] = max(maxcost[i][j-1], maxcost[a][j-1]);
                }
    }

    // 求p到q的路径上的最大权
    int query(int p, int q) {
        int tmp, log, i;
        if(L[p] < L[q]) swap(p, q);
        for(log = 1; (1 << log) <= L[p]; log++);
        log--;

        int ans = -INF;
        for(int i = log; i >= 0; i--)
            if (L[p] - (1 << i) >= L[q]) {
                ans = max(ans, maxcost[p][i]);
                p = anc[p][i];
            }

        if (p == q) return ans; // LCA为p

        for(int i = log; i >= 0; i--)
            if(anc[p][i] != -1 && anc[p][i] != anc[q][i]) {
                ans = max(ans, maxcost[p][i]);
                p = anc[p][i];
                ans = max(ans, maxcost[q][i]);
                q = anc[q][i];
            }

        ans = max(ans, cost[p]);
        ans = max(ans, cost[q]);
        return ans; // LCA为fa[p](它也等于fa[q])
    }
};

LCA solver;

#include<cstdio>
#include<vector>

int pa[maxn];
int findset(int x) {
    return pa[x] != x ? pa[x] = findset(pa[x]) : x;
}

vector<int> G[maxn], C[maxn];

void dfs(int u, int fa, int level) {
    solver.L[u] = level;
    for(int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        if(v != fa) {
            solver.fa[v] = u;
            solver.cost[v] = C[u][i];
            dfs(v, u, level+1);
        }
    }
}

struct Edge {
    int x, y, d;
    bool operator < (const Edge& rhs) const {
        return d < rhs.d;
    }
};

const int maxm = 100000;
Edge e[maxm];

int main() {
    int kase = 0, n, m, x, y, d, Q;
    while(scanf("%d%d", &n, &m) == 2 && n) {
        for(int i = 0; i < m; i++) {
            scanf("%d%d%d", &x, &y, &d);
            e[i] = (Edge) {
                x-1, y-1, d
            };
        }
        // 最小生成树
        sort(e, e+m);
        for(int i = 0; i < n; i++) {
            pa[i] = i;
            G[i].clear();
            C[i].clear();
        }
        for(int i = 0; i < m; i++) {
            int x = e[i].x, y = e[i].y, d = e[i].d, u = findset(x), v = findset(y);
            if(u != v) {
                pa[u] = v;
                G[x].push_back(y);
                C[x].push_back(d);
                G[y].push_back(x);
                C[y].push_back(d);
            }
        }
        // 化成有根树
        solver.n = n;
        dfs(0, -1, 0);
        solver.preprocess();

        if(++kase != 1) printf("\n");
        scanf("%d", &Q);
        while(Q--) {
            scanf("%d%d", &x, &y);
            printf("%d\n", solver.query(x-1, y-1));
        }
    }
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值