【NOIP2013提高组T3】货车运输-最大生成树+倍增LCA

题目大意:给出一个有N个点,M条边的图,有Q个询问,每个询问包含2个正整数i,j,要求:如果i,j不连通,输出-1,否则,对于它们之间的每条路径,得出该路径上边权的最小值,输出这个最小值的最大值。

做法:首先用Kruskal算法求出图的最大生成树,由于图可能不是连通的,因此生成的结果可能是一片森林。我们用并查集来表示点所属的集合,如果对于询问(i,j),i,j所属集合不同,那它们就是不连通的。如果它们在同一个集合里,就要求出它们之间路径上边权的最小值了,这时我们使用树上倍增算法,用f[i][j]表示i上面的第2^j个祖先,因此f[i][0]表示i的父亲节点,容易得出:f[i][j]=f[f[i][j-1]][j-1]。再用mi[i][j]表示i到f[i][j]路径上的最小边权,也可得出:mi[i][j]=min(mi[i][j-1],mi[f[i][j-1]][j-1])。在处理询问时,如果两个点的深度不同,为了更好的处理,我们用一个move函数用深度较深的点的和深度较浅的点的深度相同的祖先来替换这个深度较深的点,并记录深度较深的点到这个祖先的路径上的最小值。在这之后,如果两点相同,则直接输出结果,否则就用一个类似move的过程,不断用这两点的祖先替换这两点,直到两点重合为止。在此之前,可以先用dfs求出所有的f[i][0],mi[i][0]和每个点的深度,然后就可以用上面的方法来解决问题了。

以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define inf 99999999
using namespace std;
int n,m,q,tot=0,first[10010]={0},root[10010],mi[10010][23]={0},dep[10010],f[10010][23]={0};
bool vis[10010]={0};
struct edge {int x,y,d;} ed[50010];
struct {int v,d,next;} e[20010];

bool cmp(edge a,edge b)
{
  return a.d>b.d;
}

void insert(int a,int b,int c)
{
  e[++tot].v=b;
  e[tot].d=c;
  e[tot].next=first[a];
  first[a]=tot;
}

int find(int x)
{
  int r=x,i=x,j;
  while(root[r]!=r) r=root[r];
  while(i!=r) {j=root[i];root[i]=r;i=j;}
  return r;
}

void merge(int x,int y)
{
  int fa=find(x),fb=find(y);
  root[fa]=fb;
}

void kruskal()
{
  for(int i=1;i<=m;i++)
  {
    int fa=find(ed[i].x),fb=find(ed[i].y);
	if (fa!=fb)
	{
	  merge(ed[i].x,ed[i].y);
	  insert(ed[i].x,ed[i].y,ed[i].d);
	  insert(ed[i].y,ed[i].x,ed[i].d);
	}
  }
}

void dfs(int v)
{
  vis[v]=1;
  for(int i=first[v];i>0;i=e[i].next)
    if (!vis[e[i].v])
	{
	  dep[e[i].v]=dep[v]+1;
	  f[e[i].v][0]=v;
	  mi[e[i].v][0]=e[i].d;
	  dfs(e[i].v);
	}
}

int move(int &v,int h)
{
  int rec=inf;
  for(int i=22;i>=0;i--)
    if (dep[f[v][i]]>=h) //注意是“>=”
	{
	  rec=min(rec,mi[v][i]);
	  v=f[v][i];
	}
  return rec;
}

int query(int a,int b)
{
  if (find(a)!=find(b)) return -1;
  int rec=inf;
  if (dep[a]>dep[b]) rec=move(a,dep[b]);
  if (dep[b]>dep[a]) rec=move(b,dep[a]);
  if (a==b) return rec;
  for(int i=22;i>=0;i--)
    if (f[a][i]!=f[b][i])
	{
	  rec=min(rec,min(mi[a][i],mi[b][i]));
	  a=f[a][i];
	  b=f[b][i];
	}
  rec=min(rec,min(mi[a][0],mi[b][0]));
  return rec;
}

int main()
{
  scanf("%d%d",&n,&m);
  for(int i=1;i<=m;i++)
    scanf("%d%d%d",&ed[i].x,&ed[i].y,&ed[i].d);
  
  sort(ed+1,ed+m+1,cmp);
  for(int i=1;i<=n;i++) root[i]=i;
  kruskal(); //求最大生成树并存下在最大生成树中的边,为dfs做准备
  
  for(int i=1;i<=n;i++)
    if (!vis[i])
	{
	  dep[i]=0;
	  dfs(i);
	  f[i][0]=i;
	  mi[i][0]=inf;
	}
  
  for(int i=1;i<=22;i++)
    for(int j=1;j<=n;j++)
	{
	  f[j][i]=f[f[j][i-1]][i-1];
	  mi[j][i]=min(mi[j][i-1],mi[f[j][i-1]][i-1]);
	} //初始化f和mi数组
  
  scanf("%d",&q);
  for(int i=1;i<=q;i++)
  {
    int a,b;
	scanf("%d%d",&a,&b);
	printf("%d\n",query(a,b));
  }
  
  return 0;
}


posted @ 2016-08-03 10:38  Maxwei_wzj  阅读(80)  评论(0编辑  收藏  举报