题目描述
A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
输入输出格式
输入格式:输入文件名为 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。
大致思路:
/* 因为要求的是每个司机的载重限制的最大值,所以建一棵最大生成树,也用kruskal来做,但是边权从大到小排序,
生成的最大树上两点间的最小边权就是最大载重。
不用加两条边,加一条边就可以。
生成了树之后,处理处每个点的lca,以便求最大载重。
怎么求lca?
在跑Kruskal将两个树合并的时候,在两棵树根节点之间连一条边,把连的边保存下来
让b树的根的爸爸指向a树的根 ——>
Add_edge(father[fx],fy,edge1[i].w);
father[fy]=fx;
如果一个点的father等于他本身,那么这就是一个树根。然后从这个dfs求lca,因为可能有多棵树,所以for循环枚举一遍。
怎样判断能不能到达?
如果两个点所属的连通块不同——>find(x)!=find(y),则无法到达。
那么怎么求最大载重呢?
在dfs求lca时候,记录下每个点到他的父亲节点的距离,进行一次询问的时候,求出两个点的lca,然后一直沿着父亲节点跳,直到跳到lca为止,跳的时候取min。
我的语言阐述思路:
这是道难题,花了我两个多小时(照着题解读)也没AC。虽然暂时放置bug,但是仍想说下思路,思路很厉害。
本题给我们的是一个图,我们首先想到的可能是求出各个点之间的最短路径,但这一想就不现实,因为多源最短路径都要O(N^3)的时间复杂度(FLOYD),而单源最短路径(DIJKSTRA堆优化, SPFA)一遍也会TLE,所以我们应换一种思路思考这道题。
思路其实也就是说,根据题意构建好图后,需要用kruskal来构建最大生成树,然后针对这个最大生成树来求两点间的最近公共祖先(联想树的结构,运输沿途一定会通过这个最近公共祖先的!),然后找这两个点到最近公共祖先之间的权值最小的那条边即可。抓住性质:“最小生成树的最长边一定是所有生成树中最小的,最大生成树的最短边一定是所有生成树中最大的”。正好符合想要尽可能多装货物的题设。
之前用到kruskal通常是求最大连通子图的最短路长度==》最小生成树的权值的,不需要把真正的最小生成树构建出来,而这道题,因为我需要用lca求最大公共祖先,那么一定需要把树构造出来!(不然你怎么dfs去把每个点的父节点和深度初始化)!那么就是在并查集Merge的同时相应地建边,重新做个edge[MAX_M]出来。
其次还有个重点是,你需要注意可能题目给的图压根就不连通,所以kruskal构造出来的生成树有几个,那么其实遍历一遍把满足father[i]==i的点作为根节点,去dfs该根节点对应的连通块就好了。
倍增时我们可以参考之前的题目的经验,用一个二维数组f存储,其中f[i,j]表示第i个节点往上(根节点方向)跑2^j个点到达的点,但这样我们还是无法知道两点之间的最小边(题目要求的答案),那么就在初始化深度和父节点的时候顺带初始化数组dis[i]表示i结点和它的第一个父节点的边的权值。最后从两个点分别依次一个一个向前跳到公共祖先处,每次跳的时候更新最小边权即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 10005
using namespace std;
int n,m,num_edge1,num_edge,q,x,y,z;
int head1[N],head[N],father[N],deep[N];
int fa[N][16],dis[N],chudu[N];
struct Edge
{
int u,v,w,next;
bool operator < (Edge a) const //边权从大到小排序,生成最大树
{
return w>a.w;
}
}edge1[N*5],edge[N];
void read(int &num)
{
num=0;
char c=getchar();
for(;!isdigit(c);c=getchar());
for(;isdigit(c);c=getchar()){num=num*10+c-'0';}
}
void add_edge(int u,int v,int w)
{
edge1[++num_edge1].u=u;
edge1[num_edge1].v=v;
edge1[num_edge1].w=w;
edge1[num_edge1].next=head1[u];
head1[u]=num_edge1;
}
void Add_edge(int u,int v,int w)
{
edge[++num_edge].u=u;
edge[num_edge].v=v;
edge[num_edge].w=w;
edge[num_edge].next=head[u];
head[u]=num_edge;
}
int find(int x)
{
if(father[x]!=x) father[x]=find(father[x]);
return father[x];
}
void Kruskal()
{
for(int i=1;i<=n;i++) father[i]=i; //注意克鲁斯卡尔前要初始化并查集
sort(edge1+1,edge1+m+1);
for(int i=1;i<=m;i++) //最大生成树没有限制只能有n-1条边!
{
int fx=find(edge1[i].u),fy=find(edge1[i].v);
if(fx==fy) continue;
Add_edge(father[fx],fy,edge1[i].w); //生成的树,保存下来,用来dfs处理lca
father[fy]=fx;
}
}
void dfs(int u) //处理lca
{
for(int i=1;i<=14;i++)
{
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(int i=head[u];i;i=edge[i].next)
{
if(fa[u][0]==edge[i].v) continue;
fa[edge[i].v][0]=u;
dis[edge[i].v]=edge[i].w; //该点到第一个父亲节点的距离
deep[edge[i].v]=deep[u]+1;
dfs(edge[i].v);
}
}
int get_lca(int x,int y) //求lca
{
if(deep[x]<deep[y]) swap(x,y);
int h=deep[x]-deep[y];
for(int i=14;i>=0;i--)
{
if(h&(1<<i))
x=fa[x][i];
}
if(x!=y)
{
for(int i=14;i>=0;i--)
{
if(fa[x][i]!=fa[y][i])
{
x=fa[x][i];
y=fa[y][i];
}
}
x=fa[x][0];
}
return x;
}
int work(int x,int y) //求最大载重 //求x到f之间的最小权值的边(注:f是x的祖先
{
int a=999999999,b=999999999;
int lca=get_lca(x,y);
//printf("lca: %d\n",lca);
for(int i=x;i!=lca;i=fa[i][0]) //a往lca跳 //一个一个的跳,一条边一条边依次的看最小是多少
{
a=min(a,dis[i]);
}
for(int i=y;i!=lca;i=fa[i][0]) //b往lca跳 //一个一个的跳,一条边一条边依次的看最小是多少
{
b=min(b,dis[i]);
}
return min(a,b); //最大载重即边权的最小值
}
int main()
{
read(n),read(m);
for(int i=1;i<=m;i++)
{
read(x),read(y),read(z);
add_edge(x,y,z);
//printf("z: %d\n",z);
//printf("W: %d\n",edge1[i].w);
}
Kruskal();
for(int i=1;i<=n;i++)
{
if(find(i)==i) 若如此,则该点为根节点(不管有多少个连通块
{
fa[i][0]=i;
//printf("root: %d\n",i);
dfs(i);
}
}
read(q);
/*for(int i=1;i<=n;i++)
{
//printf("hh: %d %d\n",fa[i][0],dis[i]);
}*/
for(int i=1;i<=q;i++)
{
read(x),read(y);
if(find(x)!=find(y)) //不在同一棵树中,不能相互到达
{
printf("-1\n");
continue;
}
printf("%d\n",work(x,y));
}
fclose(stdin);
fclose(stdout);
return 0;
}
当然,我不知道比赛的时候会不会遇到这种题,因为确实码这个代码要花挺长时间的,最主要最后还不一定能跑对。但是思路确实是很有用的。