这是我这个专题做出来的第一道题,跟榜看到的B,读完题,惊了惊了!似李!货车运输!当时也不说忙着抢一血,反正就是把自己写过的代码直接粘来改改交了过了。
但这样做的意义仅仅是多一个过题数而已,还是得好好整理一下。
分析完题意后,我们知道我们需要一个最大生成树保证带宽尽可能大(因为我们不会去选择更低带宽的路),然后在一条路上找到带宽最小值。就可以搜图并记录边权最小值即可。但是这么暴力去搜基本上是必T的。我们就需要运用倍增lca来解决问题。
什么是倍增?个人理解这是一种预处理的方式,通过处理(一般以2为底数),得到区间部分的状态,在全图运作时不用每个单位每个单位计算。
比如在一张图上走7格,一步一步走需要7次运算,而如果通过这样的预处理,提前知道了22 ,21 ,20 的状态,那么只用3步就可以累加完成(4 + 2 + 1)。数字越大优化效果越明显。
那我们就来预处理吧:
FOR(i, 1, 20)
FOR(j, 1, n)
{
f[j][i] = f[f[j][i-1]][i-1]; //递推
w[j][i] = min(w[j][i-1], w[f[j][i-1]][i-1]);
}
在dfs的过程中已经有f[to][0] = now
即上一节点(处理见代码),f[to][1]就是表示倒数第二个节点位置,那么f[to][2]呢?f[to][2] = f[f[to][1]][1]
,就是f[to][1]的倒数前两个节点,累计起来就是当前节点的前第4(22)个节点,以此类推我们能完成区间节点之间长度处理。同步更新路径上带宽最小值( w[ ][ ] ),预处理就算完成了。
那么整体思路就很清晰了,找最大生成树,然后用树枝建有向图,dfs走一遍找出相邻点间关系,运用相邻点关系开始倍增预处理,因为树形结构没有环,所以单向走图搜索可以优化为双向找到公共祖先节点(LCA,而lca通常就是需要倍增来优化),这个过程结束后就能更新出整条路径的信息。
至于lca,它本身就是两个高深度的点向低深度的点回溯,必然会相遇在同一个祖先节点的过程,但是与倍增的结合后的细节操作我就放在AC代码里讲吧
#include<bits/stdc++.h>
#define maxn 200005
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define hrdg 1000000007
#define inf 0x7fffffff
#define ll long long
#define pi acos(-1.0) //一看我这些全局定义就是上个时代的版本了
using namespace std;
int n, m, u, v, d, q;
int deep[maxn], f[maxn][21], fa[maxn], w[maxn][21];
int vis[maxn];
struct node1
{
int x, y, dis;
}e[maxn*5];
struct node2
{
int to, nex, dis;
}edge[maxn*5];
int head[maxn], tot;
void add(int u, int v, int d) //前向星建立邻接表
{
tot++;
edge[tot].to = v;
edge[tot].nex = head[u];
edge[tot].dis = d;
head[u] = tot;
}
//快读
inline int read()
{
char c=getchar();long long x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
//并查集
inline int find(int x)
{
return x == fa[x] ? x : fa[x]=find(fa[x]);
}
inline void ad(int x,int y)//并
{
x=find(fa[x]);
y=find(fa[y]);
fa[x]=y;
}
inline bool check(int x,int y)//查
{
x=find(x);
y=find(y);
if(x==y) return true;
return false;
}
bool cmp(node1 x, node1 y)
{
return x.dis > y.dis; //最大生成树,从大到小排序
}
void kruskal()
{
sort(e+1, e+1+m, cmp);
FOR(i, 1, n) fa[i] = i; //并查集初始化
FOR(i, 1, m)
{
if(!check(e[i].x, e[i].y))
{
ad(e[i].x, e[i].y);
add(e[i].x, e[i].y, e[i].dis); //当可以建立生成树时,建立邻接表
add(e[i].y, e[i].x, e[i].dis);
}
}
}
void dfs(int now)
{
vis[now] = true;
for(int i=head[now]; i; i=edge[i].nex)
{
int to = edge[i].to;
if(vis[to]) continue;
deep[to] = deep[now] + 1;
f[to][0] = now;
w[to][0] = edge[i].dis;
dfs(to);
}
}
int lca(int x, int y)
{
if(!check(x, y)) return -1; //不连通
int ans = inf;
if(deep[x] > deep[y]) swap(x, y); //保证y更深
//将y提到和x相同的高度
for(int i=20; i>=0; i--)
if(deep[f[y][i]] >= deep[x])
{
ans = min(ans, w[y][i]); //一直更新最大载重
y = f[y][i]; //y向上跳
}
if(x==y) return ans; //如果已经相等
for(int i=20; i>=0; i--)
if(f[x][i] != f[y][i])
{
ans = min(ans, min(w[x][i], w[y][i])); //更新
x = f[x][i]; //同时向上跳
y = f[y][i];
}
ans = min(ans, min(w[x][0], w[y][0])); //最后再比较两个点的带宽要求
return ans;
}
int main()
{
n = read();
q = read();
m = n-1;
FOR(i, 1, m)
{
u=read(); v=read(); d=read();
e[i].x = u;
e[i].y = v;
e[i].dis = d;
}
kruskal(); //最大生成树
FOR(i, 1, n) //从1开始深搜,序列靠前的点作树根
if(!vis[i])
{
deep[i] = 1;
dfs(i);
f[i][0] = i;
w[i][0] = inf;
}
//倍增初始化
FOR(i, 1, 20)
FOR(j, 1, n)
{
f[j][i] = f[f[j][i-1]][i-1];
w[j][i] = min(w[j][i-1], w[f[j][i-1]][i-1]);
}
FOR(i, 1, q)
{
u = read(); v = read();
printf("%d\n", lca(u, v));
}
return 0;
}
/*
6 5
1 2 7
2 3 10
2 4 8
4 6 9
4 5 11
1 6
2 4
3 5
1 3
6 5
*/