【BZOJ-4289】Tax 最短路 + 技巧建图(化边为点)

题意

给出一个N个点M条边的无向图,经过一个点的代价是进入和离开这个点的两条边的边权的较大
值,求从起点1到点N的最小代价。起点的代价是离开起点的边的边权,终点的代价是进入终
点的边的边权
N<=100000
M<=200000

题解一

  1. 无向图连边时要拆成两条边,这大家都知道
  2. 然后把边看成”点”,(优化:)因为不可能每个”点”之间都能连边,所以
  3. 对除了 1点和 n点之外的点连出去的边(真实边)按权值从小到大排个序,边看作”点”
  4. 然后 i “点”向 i + 1”点”连一条边值为(化点之前的两条边的权值差);i “点”向 i - 1”点”连一条权值为0LL的边;然后每个”点”和它反向边化成的”点”连一条边值为该边以前权值的边。
  5. 然后用堆优化的dij跑一遍最短路,求出dis[i]( dis[i] = 初始点 到 i “点”的最短距离)。
  6. 最短路初始过程:将原点1连出去的 边看成点 后,加入队列。
  7. 求值:枚举连向终点n的边,维护ans = minj{dis[i^1] + val[i]}// i 是边的编号

题解二

比较有技巧的建图

首先考虑暴力点的建图:

把每条无向边拆成两条有向边.把每条边看成一个点,对于两条边a->b,b->c

在这两条边之间连有向边,边权为这两条边的权值的较大值.

新建源点S,汇点T, S向所有从1连出去的边连边,所有指向n的边向T连边. 求S->T的最短路即可.

这样的复杂度会达到O(m2)O(m2)

考虑优化一下,有个类似网络流中补流思想的方法:

考虑利用差值来建边.

依然把每条边x-y拆成x->y,y->x.

枚举每个中转点x. 将x的出边按权值排序,x的每条入边向对应的出边连该边权值的边,x的每条出边向第一个比它大的出边连两边权差值的边,x的每条出边向第一个比它小的出边连权值为0的边. 新建源汇S,T S向每条1的出边连权值为该边边权的边.每条n的入边向T连该边权值的边.

跑S->T的最短路即可.

这样的复杂度是O(mlogm)O(mlogm)就可以AC

顺带提一句,用Dijkstra效率很快

 

 

C++代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 7;

struct Edge{
    int from,to;
    int w,nxt;
}edge[maxn << 2],e[maxn << 2];

int n , m ;
int pre[maxn];
int fa[maxn],cost[maxn],dep[maxn];
int head[maxn],tot;

void init(){
    tot = 0;
    memset(head,-1,sizeof head);
    for(int i = 1;i <= n ; i++){
        pre[i] = i;
    }
}

bool cmp(Edge a,Edge b){
    return a.w < b.w;
}

void add_edge(int u ,int v,int w){
    e[tot].from = u;
    e[tot].to = v;
    e[tot].w = w;
    e[tot].nxt = head[u];
    head[u] = tot ++;
}

inline int find(int x){if(x == pre[x])return x;else return pre[x] = find(pre[x]);}

void kruskal(){
    sort(edge+1,edge+1+m,cmp);
    int fu,fv,u,v;
    for(int i = 1;i <= m; i++){
        u = edge[i].from;
        v = edge[i].to;
        fu = find(u);
        fv = find(v);
        if(fu != fv){
            pre[fu] = fv;
            add_edge(u,v,edge[i].w);
            add_edge(v,u,edge[i].w);
        }
    }
}

void dfs(int u,int Fa,int step){
    int v;
    for(int i = head[u]; ~i ;i = e[i].nxt){
        v = e[i].to;
        if(v ==Fa) continue;
        dep[v] = step;
        fa[v] = u;
        cost[v] = e[i].w;
        dfs(v,u,step + 1);
    }
}

int lca(int u,int v){
    int du = dep[u];
    int dv = dep[v];
    int res = 0;
    while(du > dv){
        res = max(res,cost[u]);
        u = fa[u];
        du --;
    }
    while(dv > du){
        res = max(res,cost[v]);
        v = fa[v];
        dv --;
    }
    while(u != v){
        res = max(res,cost[u]);
        res = max(res,cost[v]);
        u = fa[u];
        v = fa[v];
    }
    return res;
}

int main(){
    int cas = 0;
    while(cin >> n >> m){
        if(cas) puts("");
        else cas ++;
        init();
        for(int i = 1;i <= m; i ++){
            int u , v , w;
            cin >> u >> v >> w;
            edge[i].from = u;
            edge[i].to = v;
            edge[i].w = w;
        }
        //cout << 1 ;
        kruskal();
        fa[1] = cost[1] = dep[1] = 0;
        dfs(1,-1,1);
        int q;
        cin >> q;
        while(q--){
            int u , v ;
            cin >> u >> v;
        cout << lca(u,v) << endl;
        }
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/DWVictor/p/11271557.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值