题面
给出一个 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;
}

本文介绍了一种利用最大生成树和树链剖分解决特定图论问题的方法,通过Kruskal算法构建最大生成树,再使用树链剖分优化路径查询效率,实现O(logn)的查询复杂度。
835

被折叠的 条评论
为什么被折叠?



