【BZOJ-4289】Tax 最短路 + 技巧建图(化边为点)
题意
给出一个N个点M条边的无向图,经过一个点的代价是进入和离开这个点的两条边的边权的较大
值,求从起点1到点N的最小代价。起点的代价是离开起点的边的边权,终点的代价是进入终
点的边的边权
N<=100000
M<=200000
题解一
- 无向图连边时要拆成两条边,这大家都知道
- 然后把边看成”点”,(优化:)因为不可能每个”点”之间都能连边,所以
- 对除了 1点和 n点之外的点连出去的边(真实边)按权值从小到大排个序,边看作”点”
- 然后 i “点”向 i + 1”点”连一条边值为(化点之前的两条边的权值差);i “点”向 i - 1”点”连一条权值为0LL的边;然后每个”点”和它反向边化成的”点”连一条边值为该边以前权值的边。
- 然后用堆优化的dij跑一遍最短路,求出dis[i]( dis[i] = 初始点 到 i “点”的最短距离)。
- 最短路初始过程:将原点1连出去的 边看成点 后,加入队列。
- 求值:枚举连向终点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; }