题目描述
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。
输入输出样例
输入样例#1:
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
输出样例#1:
3
-1
3
说明
对于 30%的数据,0 < n < 1,000,0 < m < 10,000,0 < q< 1,000; 对于 60%的数据,0 < n < 1,000,0 < m < 50,000,0 < q< 1,000; 对于 100%的数据,0 < n < 10,000,0 < m < 50,000,0 < q< 30,000,0 ≤ z ≤ 100,000。
这道题我一开始在洛谷做的并没有看出标解,想了30分钟无果(毕竟是day1t3),看了一眼标签,竟然用到了生成树!?!又想了半天,才终于发现玄机:
首先,这道题确实需要应用到生成树,不过是克鲁斯卡尔版的“最大生成树”,原理就是每次找一个最大的边,就能起码保证某点到某点的最小重量限制最大。(这其中会用到一次并查集)
接下来,就把选出来的这n-1条边存进邻接表里。同时也预处理出这些边的权值,这里我用的数组名足以表示我对这道题难度的愤怒:zhenjibanan
最后跑一遍LCA求询问的两点之间的最小权值。
void work(int dy,int ey,int sy)
{
bian[++tmp]=ey;
zhenjibanan[tmp]=sy;
next[tmp]=first[dy];
first[dy]=tmp;
}
代码极其丑陋,写了160多行,不过代码之中用到的一些过程的样式大家可以放心借鉴甚至在别的题目中使用,基本上是非常常用实惠的模板
#include<cstdio>
#include<algorithm>
#include<iostream>
#define num 100010
struct bibi
{
int c,d;
}f[200010][20];
struct haha
{
int x,y,v;
}single[num];
using namespace std;
int dep[num],dadiaosi,dashabi,pia,qua,tmp=0; //不要看我的蜜汁变量名。。。。。。
int first[num],next[num],bian[num],zhenjibanan[num];
bool vis[num],p[num];
int dad[num],father[num],k,tot,n,m;
int single_cmp(haha a,haha b)
{
if (a.v>b.v)
return 1;
return 0;
}
int lca(int dashabi,int dadiaosi)
{
int ansa=10000000,ansb=10000000;
if (dep[dashabi]<dep[dadiaosi])
swap(dashabi,dadiaosi);
for (int i=18;i>=0;i--)
if (dep[dashabi]-dep[dadiaosi]>=(1<<i))
{
ansa=min(f[dashabi][i].d,ansa);
dashabi=f[dashabi][i].c;
}
if (dashabi==dadiaosi)
return ansa;
for (int i=18;i>=0;i--)
if (f[dashabi][i].c!=f[dadiaosi][i].c)
{
ansa=min(f[dashabi][i].d,ansa);
ansb=min(ansb,f[dadiaosi][i].d);
dashabi=f[dashabi][i].c;
dadiaosi=f[dadiaosi][i].c;
}
ansa=min(ansa,f[dashabi][0].d);
ansb=min(ansb,f[dadiaosi][0].d);
return min(ansa,ansb);
}
void dfs(int t) //与其说是建树,不如说是应用了RMQ(不会的同学可以看我的另一篇博客)思想的找爸爸。但本人在同时也找了一下该路径上的最小值
{
vis[t]=1;
for (int i=1;i<=18;i++)
{
if (dep[t]<(1<<i))
break;
f[t][i].c=f[f[t][i-1].c][i-1].c;
f[t][i].d=min(f[f[t][i-1].c][i-1].d,f[t][i-1].d);
}
for (int i=first[t];i;i=next[i])
if (!vis[bian[i]])
{
dep[bian[i]]=dep[t]+1;
f[bian[i]][0].c=t;
f[bian[i]][0].d=zhenjibanan[i];
dfs(bian[i]);
}
}
// 从这往下是两次并查集,第一次看是不是在一个连通图里,第二次在求“最大生成树”有用。
int find(int cnm)
{
if (dad[cnm]!=cnm)
return dad[cnm]=find(dad[cnm]);
}
void unionn(int xx,int yy)
{
xx=find(xx);
yy=find(yy);
if (xx!=yy)
dad[yy]=xx;
}
int find1(int cnm)
{
if (father[cnm]!=cnm)
return father[cnm]=find1(father[cnm]);
}
void unionn1(int xx,int yy)
{
xx=find1(xx);
yy=find1(yy);
if (xx!=yy)
father[yy]=xx;
}
// 从这往上是两次并查集,第一次看是不是在一个连通图里,第二次在求“最大生成树”有用。
void work(int dy,int ey,int sy)//本人比较喜欢的一种建连接表的方式:first存dy对应的第一条边的序号;next存下一条边的序号;bian存第几条边相应连着第几个点
{
bian[++tmp]=ey;
zhenjibanan[tmp]=sy; //这里很关键,建一个一维价值数组。表示第tmp条边价值是多少
next[tmp]=first[dy];
first[dy]=tmp;
}
int main()
{
// freopen("truck.in","r",stdin);
// freopen("truck.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
dad[i]=i;
for (int i=1;i<=m;i++)
{
scanf("%d%d%d",&single[i].x,&single[i].y,&single[i].v);
unionn(single[i].x,single[i].y); //并查集---询问时查看是否在一个连通图里
}
sort(single+1,single+1+m,single_cmp);
k=0;
tot=0;
for (int i=1;i<=n;i++)
father[i]=i;
while (tot<n-1) //建最大生成(没有树,树得一会建)
{
k++;
if (find1(single[k].x)==find1(single[k].y))
continue;
unionn1(single[k].x,single[k].y);
tot++;
work(single[k].x,single[k].y,single[k].v);
work(single[k].y,single[k].x,single[k].v);
}
//这才是建树
dfs(1);
int pupu;
scanf("%d",&pupu);
for (int i=1;i<=pupu;i++)
{
scanf("%d%d",&pia,&qua);
if (find(pia)!=find(qua))
{
printf("-1\n");
continue;
}
printf("%d\n",lca(pia,qua)); //纯LCA版子
}
}