题目描述
A 国有
n
座城市,编号从
输入输出格式
输入格式
- 输入文件名为
truck.in 。- 输入文件第一行有两个用一个空格隔开的整数
n,m
,表示 A 国有
n
座城市和
m 条道路。 接下来 m 行每行3 个整数 x,y,z ,每两个整数之间用一个空格隔开,表示从 x 号城市到y 号城市有一条限重为 z 的道路。注意:x 不等于 y ,两座城市之间可能有多条道路 。 - 接下来一行有一个整数
q ,表示有 q 辆货车需要运货。 - 接下来
q 行,每行两个整数 x,y ,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到y 城市,注意: x 不等于y 。 输出格式
- 输出文件名为 truck.out 。
- 输出共有
q
行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货车不能到达目的地,输出
−1 。
输入输出样例
输入样例#1
- 4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
输出样例#1
- 3
-1
3
说明
- 对于30%的数据 0<n<1,000,0<m<10,000,0<q<1,000
- 对于60%的数据 0<n<1,000,0<m<50,000,0<q<1,000
- 对于100%的数据 0<n<10,000,0<m<50,000,0<q<30,000,0≤z≤100,000
原题地址
分析 最大生成树森林 + 树链剖分
- 题目的大意就是给出无向图中的 q 对点,要求找出连接每对点的一种路径方案,使得每条路径上边权的最小值最大,并输出这个最小值。
- 因为这是无向图,于是我们做一遍最大生成树,这样每对点的答案就为生成树路径上边权的最小值。
- 简单用反证法证明一下:
- 假设两点间存在一条边不在生成树上,并且边权大于它们生成树路径上边权的最小值,那么按照
Kruskal 算法的贪心思想,我们肯定会根据边权优先选择这条边作为生成树上的边而不选择边权为最小值的那条边,这与之前的假设矛盾,因此我们得到两点的生成树路径上每条边的边权肯定都是尽量大的。 - 于是问题就被转化:给出一棵树,求树上两点路径上边权的最小值。
- 这可以用倍增 LCA 算,但我写的是树链剖分(感觉好像没什么人用)。
- 主要注意下这张图可能是不连通的,因此实际上应该是最大生成树森林,则不在同一棵树上即输出 −1 ,这可以直接用并查集判断;并且我们要对森林中的每一棵树都剖分一遍(记一个 bool 数组表示点是否被访问过,然后每找到一个没被访问过的点就以这个点为根遍历出一棵树)。
- 另外剖分的是边权而不是点权,所以我们进行转化,把边权的信息存储在所连两点中深度较大的那一个点上,然后在询问时不查询深度最小的点即可。
代码
(有点小长……)
#include <iostream> #include <cstdio> #include <algorithm> #define sL (s << 1) #define sR (s << 1 | 1) using namespace std; const int Maxn = 0x3f3f3f3f; const int N = 1e4 + 5, M = 1e5 + 5; int dep[N], sze[N], fa[N], idx[N], top[N], son[N], pos[N]; int sol[N], val[M], f[N]; bool vis[N]; int n, m, Q, P; char frd[M], *hed = frd + M; char fwt[M << 3], *opt = fwt; const char *tal = hed; inline char nxtChar() { if (hed == tal) fread(frd, 1, M, stdin), hed = frd; return *hed++; } inline int get() { char ch; int res = 0; while ((ch = nxtChar()) < '0' || ch > '9'); res = ch ^ 48; while ((ch = nxtChar()) >= '0' && ch <= '9') res = (res << 3) + (res << 1) + (ch ^ 48); return res; } inline void put(int x) { if (x > 9) put(x / 10); *opt++ = x % 10 + 48; } struct point {int l, r, w;}a[M]; struct Edge {int to, cst; Edge *nxt;}p[M], *T = p, *lst[N]; inline bool cmp(const point &x, const point &y) {return x.w > y.w;} inline void AddEdge(const int &x, const int &y, const int &z) { (++T)->nxt = lst[x]; lst[x] = T; T->to = y; T->cst = z; (++T)->nxt = lst[y]; lst[y] = T; T->to = x; T->cst = z; } inline int Find(const int &x) { if (f[x] != x) f[x] = Find(f[x]); return f[x]; } inline void Dfs1(const int &x, const int &F) { dep[x] = dep[F] + 1; fa[x] = F; sze[x] = 1; vis[x] = true; for (Edge *e = lst[x]; e; e = e->nxt) { int y = e->to; if (y == F) continue; sol[y] = e->cst; Dfs1(y, x); sze[x] += sze[y]; if (sze[y] > sze[son[x]]) son[x] = y; } } inline void Dfs2(const int &x) { int y; if (son[x]) { top[y = son[x]] = top[x]; idx[pos[y] = ++P] = y; Dfs2(y); } for (Edge *e = lst[x]; e; e = e->nxt) if (!top[y = e->to]) { top[y] = y; idx[pos[y] = ++P] = y; Dfs2(y); } } inline int Min(const int &x, const int &y) {return x < y ? x : y;} inline void Push(const int &s) {val[s] = Min(val[sL], val[sR]);} inline void Build(const int &s, const int &l, const int &r) { if (l == r) return (void)(val[s] = sol[idx[l]]); int mid = l + r >> 1; Build(sL, l, mid); Build(sR, mid + 1, r); Push(s); } inline int Query(const int &s, const int &l, const int &r, const int &x, const int &y) { if (l == x && r == y) return val[s]; int mid = l + r >> 1; if (y <= mid) return Query(sL, l, mid, x, y); else if (x > mid) return Query(sR, mid + 1, r, x, y); else return Min(Query(sL, l, mid, x, mid), Query(sR, mid + 1, r, mid + 1, y)); } inline void Swap(int &x, int &y) {x ^= y; y ^= x; x ^= y;} inline void CkMin(int &x, const int &y) {if (x > y) x = y;} inline int PaQuery(int x, int y) { int res = Maxn; while (top[x] != top[y]) { if (dep[top[x]] < dep[top[y]]) Swap(x, y); CkMin(res, Query(1, 1, n, pos[top[x]], pos[x])); x = fa[top[x]]; } if (x == y) return res; if (dep[x] < dep[y]) Swap(x, y); return Min(res, Query(1, 1, n, pos[y] + 1, pos[x])); } int main() { n = get(); m = get(); for (int i = 1; i <= m; ++i) { a[i].l = get(); a[i].r = get(); a[i].w = get(); } for (int i = 1; i <= n; ++i) f[i] = i; sort(a + 1, a + m + 1, cmp); int K = 0; for (int i = 1; i <= m; ++i) { int tx = Find(a[i].l), ty = Find(a[i].r); if (tx != ty) { f[tx] = ty; K++; AddEdge(a[i].l, a[i].r, a[i].w); } if (K == n - 1) break; } Q = get(); int x, y; for (int i = 1; i <= n; ++i) if (!vis[i]) { Dfs1(i, 0); idx[pos[i] = ++P] = top[i] = i; Dfs2(i); } Build(1, 1, n); for (int i = 1; i <= Q; ++i) { x = get(); y = get(); if (Find(x) != Find(y)) *opt++ = '-', *opt++ = '1'; else put(PaQuery(x, y)); *opt++ = '\n'; } fwrite(fwt, 1, opt - fwt, stdout); }
- 输入文件第一行有两个用一个空格隔开的整数
n,m
,表示 A 国有
n
座城市和