【NOIP2013-D1T3】货车运输
Time Limit:10000MS Memory Limit:128000K
Total Submit:95 Accepted:57
Case Time Limit:1000MS
Description
A国有n座城市,编号从1到n,城市之间有m条双向道路。
每一条道路对车辆都有重量限制,简称限重。
现在有q辆货车在运输货物,司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
Input
第一行有两个用一个空格隔开的整数 n,m,表示 A 国有n座城市和m条道路。
接下来m行,每行3个整数x,y,z,表示从x号城市到y号城市有一条限重为z的道路(x≠y,两座城市之间可能有多条道路)
接下来一行有一个整数 q,表示有 q 辆货车需要运货。
接下来q行,每行两个整数x,y,之间用一个空格隔开,表示一辆货车需要从x城市运输货物到y城市(x≠y)。
Output
输出共有 q 行,每行一个整数,表示对于这一辆货车,它的最大载重是多少。
如果货车不能到达目的地,输出-1。
Sample Input
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
Sample Output
3
-1
3
Hint
对于 30%的数据,1<=n<=1000,1<=m<=10000,1<=q<=1000;
对于 60%的数据,1<=n<=1000,1<=m<=50000,1<=q<=1000;
对于 100%的数据,1<=n<=10000,1<=m<=50000,1<=q<=30000,0<=z<=100000。
这道题也是一道LCA类的题,但是并没有那么简单了
题意大致为:给你一个图,求出其中任意两点所有路径中最短边的最大值,如果两点无法到达,输出-1
那么这道题我们就可以先用最大生成森林求出最长的路径,由于求最大生成树要将所有边从大到小排序,那么既保证了最小值最大,又避免了对重边的讨论,求最大生成森林的时候要注意并查集的写法
然后我们预处理f(i,j)表示第i个点距离为2^j的祖先,这个可以深搜整棵树再递推一下,复杂度nlogn
minx(i,j)表示第i个点到其距离为2^j的祖先上的最小权值,然后用倍增的思想俩个点往上跳一跳更新答案就可以了
然而看起来很容易,实际上代码还有许多细节需要注意,一个不小心就得找几个小时的错,一定要万分注意
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int inf=1e9;
const int maxn=10005;
int n,quest,a,b,depth[maxn],f[maxn][20],m,dis[maxn],s;
int END[maxn<<1],len[maxn<<1],NEXT[maxn<<1],last[maxn];
int father[maxn],tot,minx[maxn][20];
bool mark[maxn];
struct wk{int a,b,c;}edge[maxn*5];
bool cmp(wk a,wk b){return a.c>b.c;}
inline void _read(int &x){
char t=getchar();bool sign=true;
while(t<'0'||t>'9')
{if(t=='-')sign=false;t=getchar();}
for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
if(!sign)x=-x;
}
void insert(int a,int b,int l){
END[++tot]=b;
len[tot]=l;
NEXT[tot]=last[a];
last[a]=tot;
}
int finder(int x){return father[x]==x?x:father[x]=finder(father[x]);}
void dfs(int v,int dpt){//fs建树顺便计算f,minx数组
mark[v]=1;depth[v]=dpt;
int i,j,k=ceil(log(depth[v])/log(2));
for(i=1;i<=k;i++){
f[v][i]=f[f[v][i-1]][i-1];
minx[v][i]=min(minx[v][i-1],minx[f[v][i-1]][i-1]);
}
for(i=last[v];i;i=NEXT[i])
if(!mark[END[i]]){
f[END[i]][0]=v;
minx[END[i]][0]=len[i];
dfs(END[i],dpt+1);
}
}
int LCA(int x,int y){
int i,k,ans=inf;
if(depth[x]<depth[y])swap(x,y);
k=depth[x]-depth[y];
for(i=0;i<=s;i++)
if(k&(1<<i)){
ans=min(ans,minx[x][i]);//这里的两个赋值语句绝对不能换位置,因为计算的时候是计算的跳到祖先之前的x点
x=f[x][i];
}
if(x==y)return ans;
k=ceil(log(depth[x])/log(2));
for(i=k;i>=0;i--)
if(f[x][i]!=f[y][i]){
ans=min(ans,min(minx[x][i],minx[y][i]));
x=f[x][i],y=f[y][i];
}
if(f[x][0]==n+1)return inf;
return min(ans,min(minx[x][0],minx[y][0]));
}
void kruscal(){//最大生成森林
int fx,fy,i;
for(i=1;i<=m;i++){
fx=finder(edge[i].a);
fy=finder(edge[i].b);
if(fx!=fy){
father[fx]=fy;
insert(edge[i].a,edge[i].b,edge[i].c);
insert(edge[i].b,edge[i].a,edge[i].c);
}//计算了一组之后要将其存边以便之后的计算
}
for(i=1;i<=n;i++)
if(finder(i)==i)insert(n+1,i,inf);//将N+1当做虚拟节点
}
int main(){
_read(n);_read(m);
int i,j,k;
for(i=1;i<=m;i++){
_read(edge[i].a);
_read(edge[i].b);
_read(edge[i].c);
}
for(i=1;i<=n;i++)father[i]=i;
sort(edge+1,edge+1+m,cmp);
<span style="font-family:Times New Roman;">kruscal();</span>
s=ceil(log(n)/log(2));
for(i=1;i<=n;i++)
for(j=0;j<=s;j++)
minx[i][j]=inf;//初值不能忘
f[n+1][0]=n+1,minx[n+1][0]=inf;
dfs(n+1,1);
_read(quest);
for(i=1;i<=quest;i++){
_read(a);_read(b);
int ans=LCA(a,b);
printf("%d\n",ans==inf?-1:ans);
}
}