题面
给出一个 n 点 m 边无向图,每一条边上有重量限制,有 q 辆车询问从 u 到 v 的最大运载重量(即路径上的最小重量限制)
1 <= n < 104,1 <= m < 5×104,1 <= q < 3×104,权值z满足0 <= z <= 105
分析
经过判断发现走某一些路是不利的,就像 dijkstra 那里的松弛操作,这里的目的是即便走的路径长,但是路径上最小的权值大就行,这些不利的路可以删去,剩下的就是最大生成树
能在不利边上跑的,一定能在最大生成树上跑,反之不一定,这就作为证明。
用 kruskal 跑出最大生成树即可
可能有多个连通块,所以最后的查询还要借助并查集的 find
现在问题变成了怎么在树上求两点间路径权值的最大值,不难想到一些树上操作如倍增,树链剖分。
这里采用树链剖分,使得每一次查询复杂度在 O(logn)
然后求一系列区间上的最小值即可求出最强的约束条件,也即载重上限
注意是边权树,和往常一样,采取将边权下放到点权的做法,对于树根,节点值赋大于z的上界,这样在取 min 时对数据无影响。
注意,查询时,当两点处于同一个重链上的情况,查询起点 +1,这是因为:
粗线是重链,u 从左下跳上来,因为权值下放,所以 u 上的权是 u 上面那条边的,而 u→v 的权值并不包含它,所以 u → v 的权值实际上只计算t和v的权值,这里的t就是 u+1 的位置
有时也许 u+1 会大于 v 。。。也就是说u从细线直接跳到 v 上了,这种情况 +1 可能会出锅。。。但是实际上AC了。。。这种特例可以考虑特判
之后就是维护剖出来的最大值了,代码上采用了树状数组维护区间最值
代码
#include "cstdlib"
#include <iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<string.h>
#include<string>
using namespace std;
int N, M;//N节点M边
int p[10005];//并查集
struct line//存边
{
int w, u, v;//权重,起始,结束
}liner[50010];
int A[10005];//用树状数组最大值维护A
int B[10005];//原数组
#define lowbit(x) (x&(-x))
class Binary_Indexed_Trees_max//区间最大值
{
private:
const static int MAXN = 10010;
int C[MAXN];
public:
void updata(int x)
{
int lx, i;
while (x <= N)
{
C[x] = A[x];
lx = lowbit(x);
for (i = 1; i < lx; i <<= 1)
C[x] = min(C[x], C[x - i]);
x += lowbit(x);
}
}
int query(int x, int y)
{
int ans = 1000000;
while (y >= x)
{
ans = min(A[y], ans);
y--;
for (; y - lowbit(y) >= x; y -= lowbit(y))
ans = min(C[y], ans);
}
//C[y]是[ y-lowbit(y)+1 , y ]内的最大值
//y-lowbit(y) > x ,则query(x,y) = max( C[y] , query(x, y-lowbit(y)) );
//y-lowbit(y) <=x,则query(x,y) = max( A[y] , query(x, y-1);
return ans;
}
}BIT;
void sswap(int& a, int& b)
{
int t = b;
b = a;
a = t;
}
class Tree_Chain {
private:
const static int MAXN = 10005;
int fa[MAXN];//当前节点的父节点
int son[MAXN];//节点的重儿子
int top[MAXN];//重链顶
int dep[MAXN];//当前节点深
int l[MAXN];//dfs序
std::vector<pair<int, int> >linker[MAXN];//存边
public:
int dfs_clock = 0;
int siz[MAXN];//表示其子树的节点数
void dfs1(int x)//x是当前节点(当前树根)
{
int cur;
siz[x] = 1;
for (int i = 0; i < linker[x].size(); i++)
{
cur = linker[x][i].first;
if (cur != fa[x]) //不是fa
{
B[cur] = linker[x][i].second;
dep[cur] = dep[x] + 1;//更新dep
fa[cur] = x;//更新fa
dfs1(cur);
siz[x] += siz[cur];//更新siz
if (siz[cur] > siz[son[x]])son[x] = cur;//更有可能成为重儿子
}
}
}
void dfs2(int x, int t)//当前节点x,当前重链顶的编号
{
l[x] = ++dfs_clock;//更新dfs序
A[dfs_clock] = B[x];//换序映射
top[x] = t;
if (son[x])dfs2(son[x], t);//继续连重链
int cur;
for (int i = 0; i < linker[x].size(); i++)
{
cur = linker[x][i].first;
if (cur != fa[x] && cur != son[x])//非父亲非重儿子
dfs2(cur, cur);//在其他儿子上找更小一些的
}
}
void insert(int& u, int& v, int& w)
{
linker[u].push_back(make_pair(v, w));
linker[v].push_back(make_pair(u, w));
}
int Lca(int& u, int& v)
{
while (top[u] != top[v])
{
if (dep[top[u]] > dep[top[v]])u = fa[top[u]];
else v = fa[top[v]];
}
return dep[u] < dep[v] ? u : v;
}
void flush()
{
dfs_clock = 0;
for (int i = 0;i < MAXN;i++)linker[i].clear();
memset(son, 0, MAXN * sizeof(int));
memset(dep, 0, MAXN * sizeof(int));
}
int calmax(int& u, int& v)
{
int ans =1000000;
while (top[u] != top[v])
{
if (dep[top[u]] < dep[top[v]])sswap(u, v);
ans = min(BIT.query(l[top[u]], l[u]),ans);//l[top[u]]到l[u]是dfs序上连续的一段
u = fa[top[u]];
}
if (dep[u] > dep[v])sswap(u, v);//到了一个重链上,切换到u比v浅的状态
ans = min(BIT.query(l[u]+1, l[v]),ans);//因为下放边权到点权,所以+1
return ans;
}
}TC;
int cmp(line i, line j) { return i.w > j.w; }//用于sort进行从大到小排序,跑最大生成树
int find(int num)//并查集-寻找顶端的函数
{
while (p[num] != num)num = p[num];
return num;
}
void Kruskal()
{
int temp;
sort(liner, liner + M, cmp);//贪心取
for (int i = 0; i < M; i++)
{
if (find(liner[i].u) != find(liner[i].v))
{
temp = find(liner[i].v);
p[find(liner[i].u)] = temp;//并查集合并
p[liner[i].u] = temp;//路径压缩
p[liner[i].v] = temp;
TC.insert(liner[i].u, liner[i].v, liner[i].w);//插入树链剖分
}
}
}
int main()
{
cin >> N >> M;
for (int i = 1; i <= N; i++)p[i] = i;
for (int i = 0; i < M; i++)cin >> liner[i].u >> liner[i].v >> liner[i].w;
Kruskal();
for (int i = 1; i <= N; i++)
{
if (!TC.siz[i]) { B[i] = 1000000; TC.dfs1(i);TC.dfs2(i, i); }
}
for (int i = 1; i <= N; i++)BIT.updata(i);//维护到最大值
int q,u,v;
cin >> q;
for (int i = 0; i < q; i++)
{
cin >> u >> v;
if (find(u) != find(v))cout << -1 << endl;//不在同一个连通块,没有路径
else {
cout << TC.calmax(u, v)<<endl;
}
}
return 0;
}