CF600 div2 F.Cheap Robot(思维+最短路+最小瓶颈路)
最开始啃这题的时候我还是个不会$lca$的人,看代码看的没有一点头绪,现在趁着寒假补了很多关于图论的知识点,回头在看这题还是有很多值得学习的地方。
Solution 1 (offline):
原题解:
Sort edges by new weight. Add them progressively, maintaining connexity with DSU.
As soon as two endpoints of a query become connected, we should put current capacity (i.e. new weight of the last edge added) as answer for this query.
To effeciently detect this, we can put tokens on endpoints of each query, and each time we do union (of DSU), we make tokens go up to the parent. If we do union by rank, each token will move at most$O(logn)$times.
离线的做法,我看了好久,开始的时候不太理解,后来看某$OI$选手的启发式合并的博客,我才有点感觉了。
先把新边按权值排序,然后逐步加边,将端点进行启发式合并(实际上就是最小生成树,考虑MST中每条边对答案的贡献)
比如我先加入一条1->3的边,那么我就去找和1相关的查询又没有在3所在的集合中
如果有就记录答案,因为有的话说明查询的两点间路径经过1->3这条边,又由于树中两点路径唯一,边是从小到大加进来的,一旦联通当前边的权值就是答案
至于为什么不去找和3相关的查询有没有在1所在的集合中,是因为预处理查询的时候加入的点对是双向的(可以自己脑补下,不太好描述)
没有的话就合并查询,由于边的加入使得这两个点联通,那么对应的查询也应该合并。
那么该如何合并呢?当然是启发式合并,其实就是小的集合并到大的集合里面。其实怎么合并都可以,答案都是对的,但是会出现这种情况:
原因是合并采取的方式是vector数组push_back(),假如你每次都是把大集合push_back()到小集合里面,那么内存可能会爆掉
看下代码应该就知道了:
#include <bits/stdc++.h> #define pb push_back #define ll long long #define fi first #define se second #define inf 1e15 using namespace std; const int maxn = 3e6+10; int n, m, k, q; int f[maxn], head[maxn], cnt; ll dis[maxn], ans[maxn]; priority_queue<pair<ll, int>> que; bool vis[maxn]; vector<pair<int,int>> tmp[maxn]; struct node1{ int to, next; ll w; }e[maxn<<1]; struct node2{ int u, v; ll w; bool operator < (const node2 &x) const{ return w<x.w; } }edge[maxn]; //不能进行路径压缩, 那样会破坏原有的信息 int find(int x){ return f[x]==x ? x : find(f[x]); } void add(int u, int v, ll w){ e[++cnt].to = v; e[cnt].w = w; e[cnt].next = head[u]; head[u] = cnt; } void dijkstra(){ for(int i = 1; i <= k; i++) dis[i] = 0, que.push({0, i}); for(int i = k+1; i <= n; i++) dis[i] = inf; while(!que.empty()){ int u = que.top().second; que.pop(); if(vis[u]) continue; vis[u] = 1; for(int i = head[u]; i; i = e[i].next) if(dis[u]+e[i].w<dis[e[i].to]){ dis[e[i].to] = dis[u] + e[i].w; que.push({-dis[e[i].to], e[i].to}); //默认优先级较大, 所以需要*(-1) } } } int main(){ scanf("%d%d%d%d", &n, &m, &k, &q); for(int i = 1; i <= m; i++){ int u, v; ll w; scanf("%d%d%lld", &u, &v, &w); add(u, v, w), add(v, u, w); edge[i] = node2{u, v, w}; } dijkstra(); for(int i = 1; i <= m; i++) edge[i].w += dis[edge[i].u]+dis[edge[i].v]; sort(edge+1, edge+1+m); for(int i = 1; i <= q; i++){ int u, v; scanf("%d%d", &u, &v); tmp[u].pb({v, i}), tmp[v].pb({u, i}); } for(int i = 1; i <= n; i++) f[i] = i; for(int i = 1; i <= m; i++){ int t1 = find(edge[i].u), t2 = find(edge[i].v); if(t1==t2) continue; if(tmp[t1].size()>tmp[t2].size()) swap(t1, t2); for(auto it:tmp[t1]){ //if(ans[it.fi]) continue; if(find(it.fi)==t2) ans[it.se] = edge[i].w; else tmp[t2].pb(it); //合并查询 } f[t1] = t2; } for(int i = 1; i <= q; i++) printf("%lld\n", ans[i]); }
Solution 2 (online):
在线做法这就比较直接,直接用倍增$lca$ / $Kurskal$生成树+$lca$求最小瓶颈路就行了,思路很清晰
正好也学了树链剖分,可以写写练下手
#include <bits/stdc++.h> #define ll long long #define inf 1e15 using namespace std; const int maxn = 6e5+10; int n, m, k, q, u, v, cost, t1, t2; int head1[maxn], head2[maxn]; int f[maxn], dep[maxn], son[maxn], size[maxn], top[maxn]; ll w[maxn], dis[maxn]; priority_queue<pair<ll, int>> que; bool vis[maxn]; struct edge{ int to, next; ll w; }e1[maxn<<1], e2[maxn]; struct node{ int u, v; ll w; bool operator < (const node &x) const{ return w < x.w; } }data[maxn]; //路径压缩 int find(int x){ return f[x]==x ? x: f[x]=find(f[x]); } void dijkstra(){ for(int i = 1; i <= k; i++) dis[i] = 0, que.push({0, i}); for(int i = k+1; i <= n; i++) dis[i] = inf; while(!que.empty()){ int u = que.top().second; que.pop(); if(vis[u]) continue; vis[u] = 1; for(int i = head1[u]; i; i = e1[i].next) if(dis[u]+e1[i].w<dis[e1[i].to]){ dis[e1[i].to] = dis[u] + e1[i].w; que.push({-dis[e1[i].to], e1[i].to}); } } } void add1(int u, int v, int w){ e1[++t1].next = head1[u]; e1[t1].to = v, e1[t1].w = w; head1[u] = t1; } void add2(int u, int v){ e2[++t2].next = head2[u]; e2[t2].to = v; head2[u] = t2; } void dfs1(int u, int fa){ size[u] = 1; for(int i = head2[u]; i; i = e2[i].next){ int v = e2[i].to; if(v==fa) continue; dep[v] = dep[u] + 1, f[v] = u; dfs1(v, u), size[u] += size[v]; if(size[v]>size[son[u]]) son[u] = v; } } void dfs2(int u, int topf){ top[u] = topf; if(son[u]) dfs2(son[u], topf); for(int i = head2[u]; i; i = e2[i].next){ int v = e2[i].to; if(v==son[u]||v==f[u]) continue; dfs2(v, v); } } int lca(int u, int v){ while(top[u]!=top[v]){ if(dep[top[u]]<dep[top[v]]) swap(u, v); u = f[top[u]]; } return dep[u]<dep[v] ? u : v; } int main(){ scanf("%d%d%d%d", &n, &m, &k, &q); for(int i = 1; i <= m; i++){ scanf("%d%d%d", &u, &v, &cost); add1(u, v, cost), add1(v, u, cost); data[i] = node{u, v, cost}; } dijkstra(); for(int i = 1; i <= m; i++) data[i].w += dis[data[i].u]+dis[data[i].v]; sort(data+1, data+1+m); for(int i = 1; i <= n; i++) f[i] = i; int cnt = n; for(int i = 1; i <= m; i++){ int fu = find(data[i].u), fv = find(data[i].v); if(fu==fv) continue; w[++cnt] = data[i].w; f[cnt] = f[fu] = f[fv] = cnt; add2(cnt, fu), add2(cnt, fv); } dfs1(cnt, 0), dfs2(cnt, cnt); while(q--){ scanf("%d%d", &u, &v); printf("%lld\n", w[lca(u, v)]); } }