【NOIP2013提高组day1】货车运输

【NOIP2013提高组day1】货车运输_第1张图片
额呵呵,找到一个照片开始乱搞ing。(嘻嘻

car-driver

    • 前奏
    • 题目大意
    • 题目解法
        • 最小生成树
        • LCA

前奏

让我们进入正题,这道题目所涉知识面是很广的。
先来列举一下这道题的知识点:最小生成树(最好是克鲁斯卡尔,也就是说我们还要用到并查集)+LCA(最好是倍增的)+(链式前向星)
*其实我们这道题要应用的是最大生成树,但是其实和最小生成树差异不大,所以在后文我还是叫最小生成树。

所以对上面知识有问题的同学,建议去看一下以下文章:

最小生成树详细解析

链式前向星

倍增求LCA

LCA连我自己都还没找到比较好的博客,所以,有时间自己复习梳理的时候写一篇。


题目大意

给出你一幅图和一些边,每条边有一些权值,两两城市间会有不止一条边
问你从x城市到y城市的所有可行的路径方案中,最小边的最大值是多少
若两城市不连通则输出-1

对于 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。


题目解法

因为数据比较大,所以我们这题不讲部分分。
因为直接暴力不是0分就是玄学水到20。
两个步骤,最小生成树+LCA(最近公共祖先)

最小生成树

相对于prim,克鲁斯卡尔会快一点。所以我们选择性能优异的后者,而后者的思想主要是这样的:
先排序,找到从小到大的权值的边的序列。(因为是最大生成树,所以这里是从大到小。
而我们要一个个去处理这些边,如果这些边的起始点和终点并不连接的话,我们就要保留这条边,所以在这里我们要用到并查集。我们可以将进行合并操作,使得这棵树的边数最好缩小到节点数-1,而联通所有的节点。

if(find(a[i].u)!=find(a[i].v))
	fa[find(a[i].u)]=find(a[i].v)

并且,我们在这里可以用到链式前向星存边了。可以比矩阵快一点。

add(a[i].u,a[i].v,a[i].dis)

LCA

我们知道,这样建出来的树一定是可以求出符合题意答案的树,所以我们直接考虑找答案。
为了让我们的答案不让别的因素影响。
举个例子,如果多走了一条边,这条边的边权比我们的答案要大,其实对我们的答案没有影响,因为我们要选的答案是路径上权值最小的一条边。
如果这条边的权值比我们的答案要小,其实对我们的答案还有不利影响。

这里讲倍增的方法;
先预处理出每个节点的2的幂次祖先,然后在 O ( l o g ( n ) ) O(log(n)) O(log(n))的时间复杂度上求出路径的极值。然后,我们就要用到一个很重要的式子,它的用处是转移F数组的值:
f [ x ] [ i ] = f [ f [ x ] [ i − 1 ] ] [ i − 1 ] ; f[x][i]=f[f[x][i-1]][i-1]; f[x][i]=f[f[x][i1]][i1];
然后,为了符合题意,我们就要求LCA中的最小值。
在倍增的方法中,我们需要选择一种“跳”的方法,将两个数的点跳到同一层,这样可以使得我们求的LCA就是他们上一层的节点。
然后,我们还需要用到一个数组来记录一下就好了。

#include
using namespace std;
int n,m,q,x,y;
int fa[10005];
int next[20005],first[20005],go[20005],dis[20005],tot;
int dep[10005],f[10005][30],fm[10005][30];
struct node{
     
	int u,v,dis;
}a[50005];
int read()
{
     
	int f=1,x=0;
	char s=getchar();
	while(s<'0'||s>'9')
	{
     
		if(s=='-') f=-1;
		s=getchar();
	}
	while(s>='0'&&s<='9') x=x*10+s-'0',s=getchar();
	return x*f;
}
void qsort(int l,int r)
{
     
	int i=l,j=r,mid=a[(l+r)/2].dis;
	while(i<=j)
	{
     
		while(a[i].dis>mid) i++;
		while(a[j].dis<mid) j--;
		if(i<=j) swap(a[i],a[j]),i++,j--;
	}
	if(l<j) qsort(l,j);
	if(i<r) qsort(i,r);
}
void add(int u,int v,int len)//链式前向星 
{
     
	next[++tot]=first[u];first[u]=tot;go[tot]=v,dis[tot]=len;
	next[++tot]=first[v];first[v]=tot;go[tot]=u,dis[tot]=len;
}
int find(int x)
{
     
	if(fa[x]==x) return x;
	return fa[x]=find(fa[x]);
}
void ready(int o,int from)
{
     
	dep[o]=dep[from]+1;
	for(int i=1;i<=20;i++){
     
		f[o][i]=f[f[o][i-1]][i-1];
		fm[o][i]=min(fm[f[o][i-1]][i-1],fm[o][i-1]);
	}
	for(int i=first[o];i;i=next[i]){
     
		int v=go[i];
		if(v==from)continue;
		fm[v][0]=dis[i];
		f[v][0]=o;
		ready(v,o);
	}
}
int lca(int x,int y)
{
     
	int minx=0x7fffffff;
	if(dep[y]>dep[x]) swap(x,y);
	for(int i=20;i>=0;i--){
     
		if(dep[f[x][i]]>=dep[y]){
     
			minx=min(minx,fm[x][i]);
			x=f[x][i];
		}
		if(x==y) return minx; 
	}
	for(int i=20;i>=0;i--){
     
		if(f[x][i]!=f[y][i]){
     
			minx=min(minx,min(fm[x][i],fm[y][i]));
			x=f[x][i];
			y=f[y][i];
		}
	}
	return min(minx,min(fm[x][0],fm[y][0]));
}
int main()
{
     
	n=read(),m=read();
	for(int i=1;i<=n;++i) fa[i]=i;
	for(int i=1;i<=m;++i) a[i].u=read(),a[i].v=read(),a[i].dis=read();
	qsort(1,m);
	for(int i=1;i<=m;++i)
		if(find(a[i].u)!=find(a[i].v))
			fa[find(a[i].u)]=find(a[i].v),add(a[i].u,a[i].v,a[i].dis);
	ready(1,0);
	q=read();
	for(int i=1;i<=q;++i)
	{
     
		x=read(),y=read();
		if(find(x)!=find(y)) printf("-1\n"); 
		else printf("%d\n",lca(x,y));
	}
	return 0;
}

你可能感兴趣的:(LCA,最小生成树)