题意:有n座城市,其间有m条道路相连,构成无向图,图中可能存在孤点。每条道路有一个限重值,货车走这条道路时所载货物的重量不应超过限重值。有q次询问,每次询问给出两个城市,问从城市1到城市2之间货车的最大载重是多少。
思路:从第一个城市 即节点1开始建立一颗最大生成树,然后在树上进行LCA操作,找出最近公共祖先,两个节点之间最小边权的最大值(即为题中的最大载重)。
1.先构造最大生成树,用Kruskal构造。和最小生成树的方法一样,条件改成取较大值就行。
2.预处理yuchuli构造倍增数组。
3.LCA函数,一个简单的查询操作。(转载自)LCA教程
x、y分别是树上的两个点,找他们的最近的公共祖先
开头用一个判断,规定 x 的深度一定比 y 大,否则就用 swap函数 交换一下。
第一个循环是:让 x 往上跳,去找 ‘y的所在层’
如果 x 和 y 重合了,就已经是答案了;
否则 让 x 和 y 一起向上跳,跳到 ‘拥有共同父亲的下一层’
轻轻地输出这位父亲,完成~~~
先上代码。代码里面p[][]数组对应后面记录的dp[][]数组,代码里的dp[][]数组用来存边权的。
/*
* Do not go gentle into that good night
* ----Dylan Thomas
* Author: looooop
* Created Time: 2018年12月09日 星期日 16时02分22秒
* File Name: p1967.cpp
*/
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <math.h>
#include <bitset>
#include <algorithm>
#include <climits>
using namespace std;
#define lson 2*i
#define rson 2*i+1
#define LS l,mid,lson
#define RS mid+1,r,rson
#define UP(i,x,y) for(i=x;i<=y;i++)
#define DOWN(i,x,y) for(i=x;i>=y;i--)
#define MEM(a,x) memset(a,x,sizeof(a))
#define W(a) while(a)
#define gcd(a,b) __gcd(a,b)
#define LL long long
#define N 1000005
#define MOD 1000000007
#define INF 0x3f3f3f3f
#define EXP 1e-8
#define lowbit(x) (x&-x)
#define MAX 100006
int n,m;
int x,y,z;
int q,tot;
struct node1{
int x,y,c;
};
struct node2{
int x,y,c,g;
};
int vis[MAX],head[MAX],fa[MAX],deep[MAX];
int dp[MAX][26],p[MAX][26];
node1 G[MAX];
node2 tree[MAX];
void init(){
for(int i = 1; i <= n; i++)
fa[i] = i;
MEM(vis,0);MEM(deep,0);
MEM(head,0);
}
bool cmp(node1 a,node1 b){
return a.c > b.c;
}
int Find(int x){
if(x != fa[x]){
return fa[x] = Find(fa[x]);
}
return x;
}
void conn(int x,int y,int value){
tot++;
tree[tot].x = x;
tree[tot].y = y;
tree[tot].c = value;
tree[tot].g = head[x];
head[x] = tot;
}
void Kruskal(){
//sort(G+1,G+m+1,cmp);
int cnt = 0;
for(int i = 1; i <= m; i++){
int xFa = Find(G[i].x);
int yFa = Find(G[i].y);
if(xFa != yFa){
fa[xFa] = yFa;
cnt++;
conn(G[i].x,G[i].y,G[i].c);
conn(G[i].y,G[i].x,G[i].c);
if(cnt == n-1) break;
}
}
}
void dfs(int st){
for(int i = head[st]; i ; i = tree[i].g){
int v = tree[i].y;
if(deep[v] == 0){
deep[v] = deep[st] + 1;
p[v][0] = st;
dp[v][0] = tree[i].c;
dfs(v);
}
}
}
void yuchuli(){
for(int i=1;i<=n;i++){
if(deep[i] == 0){
deep[i] = 1;
p[i][0] = 0;
dfs(i);
}
}
dfs(1);
for(int i=1;i<=20; i++){
for(int j = 1; j <= n; j++){
p[j][i] = p[p[j][i-1]][i-1];
dp[j][i] = min(dp[j][i-1],dp[p[j][i-1]][i-1]);
}
}
}
int lca(int x,int y){
int ans = INF;
if(deep[x] < deep[y]) swap(x,y);
for(int i=20; i >= 0; i--){
if(deep[p[x][i]] >= deep[y]){
ans = min(ans,dp[x][i]);
x = p[x][i];
}
}
if(x == y) return ans;
for(int i = 20; i>=0;i--){
if(p[x][i]!=p[y][i]){
ans = min(ans,min(dp[x][i],dp[y][i]));
x = p[x][i];
y = p[y][i];
}
}
ans = min(ans,min(dp[x][0],dp[y][0]));
return ans;
}
int main(int argc,char *argv[]){
scanf("%d%d",&n,&m);
for(int i =1; i <= m; i++){
scanf("%d%d%d",&G[i].x,&G[i].y,&G[i].c);
}
//sort(G+1,G+m+1,cmp);
sort(G+1,G+m+1,cmp);
//for(int i = 1; i <= m; i++)
// printf("%d\n",G[i].c);
//printf("\n");
init();
Kruskal();
yuchuli();
//int q;
scanf("%d",&q);
for(int i = 1; i <= q; i++){
int a,b;
scanf("%d%d",&a,&b);
if(Find(a) != Find(b))
//printf("Find(a)=%d Find(b)=%d\n",Find(a),Find(b)),printf("-1\n");
printf("-1\n");
else{
int ANS = lca(a,b);
printf("%d\n",ANS);
}
}
return 0;
}
再记录一下倍增求LCA的方法,根据上文提到的链接。
LCA是求树上最近公共祖先的方法,主要有Tarjan的深搜离线操作和倍增数组的在线操作。这里记录倍增在线LCA
1.确定树的根,使树的根层数是1,定义dp[r][0]作为边界,往上跳就出界了
deep[r] = 1; dp[r][0] = 0;
2.构造树,主要把点都分好层,做好深度标记,使用dfs来实现,让凌乱的图变成一棵树deep[y]表示y号点的深度,层数。dp{y][0]表示y点向上跳第一步的时候(也是跳一步,2^0步)必然是自己父亲。
deep[y] =deep[x] + 1; dp[y][0] = x;
3.
通过两层循环,把倍增数组构造出来:
第一层循环是步数,从小到大;
第二层循环是点数,有的人习惯把点数写成20,其实根据实际log(n)/log(2)就够用了,因为你想一想,2^20是多少
//=================================
这是本次题解最难最难最难的东东!!
原理类似:合并石子:dp的经典思路,先把小规模的状态都算出来,再层层推出大规模的数据。
//=================================
4.最后一步,LCA函数
x、y分别是树上的两个点,找他们的最近的公共祖先
开头用一个判断,规定 x 的深度一定比 y 大,否则就用 swap函数 交换一下。
第一个循环是:让 x 往上跳,去找 ‘y的所在层’
如果 x 和 y 重合了,就已经是答案了;
否则 让 x 和 y 一起向上跳,跳到 ‘拥有共同父亲的下一层’
轻轻地输出这位父亲,完成~~~