Luogu P2245 星际导航

题目描述

sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好。为了方便起见,我们可以认为宇宙是一张有N 个顶点和M 条边的带权无向图,顶点表示各个星系,两个星系之间有边就表示两个星系之间可以直航,而边权则是航行的危险程度。

sideman 现在想把危险程度降到最小,具体地来说,就是对于若干个询问(A, B),sideman 想知道从顶点A 航行到顶点B 所经过的最危险的边的危险程度值最小可能是多少。作为sideman 的同学,你们要帮助sideman 返回家园,兼享受安全美妙的宇宙航行。所以这个任务就交给你了。

输入输出格式

输入格式:

第一行包含两个正整数N 和M,表示点数和边数。

之后 M 行,每行三个整数A,B 和L,表示顶点A 和B 之间有一条边长为L 的边。顶点从1 开始标号。

下面一行包含一个正整数 Q,表示询问的数目。

之后 Q 行,每行两个整数A 和B,表示询问A 和B 之间最危险的边危险程度的可能最小值。

输出格式:

对于每个询问, 在单独的一行内输出结果。如果两个顶点之间不可达, 输出impossible。

输入输出样例

输入样例#1:

4 5
1 2 5
1 3 2
2 3 11
2 4 6
3 4 4
3
2 3
1 4
1 2

输出样例#1:

5
4
5

说明

对于40% 的数据,满足N≤1000,M≤3000,Q≤1000。

对于 80% 的数据,满足N≤10000,M≤10^5,Q≤1000。

对于 100% 的数据,满足N≤10^5,M≤3×10^5,Q≤10^5,L≤10^9。数据不保证没有重边和自环。

Solution

主要算法:Kruskal算法构造最小生成树+数组模拟邻接表存储+最近公共祖先(LCA)查询。

本题给我们的是一个图,我们首先想到的可能是求出各个点之间的最短路径,但这一想就不现实,因为多源最短路径都要O(N^3)的时间复杂度,而单源最短路径一遍也会TLE,所以我们应换一种思路思考这道题。

我们敏锐的发现,这题似乎与NOIP2013Day1T3十分相似(几乎就是一样的,这篇题解的意义何在qwq),类似的题目还有NOIP2012Day2T3与NOIP2015Day2T3,这些题目的共同点都是在树上倍增跑,并且与NOIP2015Day2T3一样都用到了LCA,所以我们可以把它变成一棵树,这样我们就用到了最小生成树。这样,把它变成一棵树后再按之前的套路倍增啊,LCA啊之类的,就可以很容易的把这题A了。

现在我们首先要解决的是最小生成树的问题,到底是用Prim算法还是用Kruskal算法呢?我们知道,Prim算法要每次循环一遍找最小边权,这样时间复杂度就是O(N^2)了(但用堆什么的就另当别论了qwq),而Kruskal算法若用快排可以把时间复杂度减到O(N*logN)(应该是吧qwq),因此Prim算法会TLE,而Kruskal算法不会,故我们用Kruskal算法。

接下来Kruskal算法筛下来的边用数组模拟的邻接表存储下来,开始建树。(这里注意把每一棵树都建好,我试过只建含有编号1的树,竟也A了,但我们还是严谨一点,不要水数据吧qwq~但没想到只建含有编号1的树NOIP2013Day1T3这样也A了,数据是有多水

接下来就是查询操作了。这里我使用的是倍增算法,倍增时我们可以参考之前的题目的经验,用一个二维数组f存储,其中f[i,j]表示第i个节点往上(根节点方向)跑2^j个点到达的点,但这样我们还是无法知道两点之间的最大边(题目要求的答案),所以我们可以再用一个二维数组s存储,s[i,j]表示第i个节点往上(根节点方向)跑2^j个点中的最大边,这样我们预处理完,就可以用log级别的时间复杂度来完成查询了。倍增算法的LCA我这里就不再叙述了。关于其他方面,就只要当两点的代表不是同一个点时特判一下输出“impossible”就可以了。

——————分割不完全的分割线——————

pascal代码如下:

uses math;
var n,m,i,j,k,l,r,ans:longint;
a,be,tar,next,x,y,len:array[1..300000]of longint;
f,s:array[0..100000,0..17]of longint;
b:array[1..100000]of boolean;
c,last,floor,find:array[0..100000]of longint;
function go(x:longint):longint;//并查集
begin
  if c[x]<>x then
  go:=go(c[x])
  else
  go:=x;
  c[x]:=go;
end;
procedure sort(l,r:longint);//Kruskal算法先按边权排序
var i,j,xx,yy:longint;
begin
  i:=l;
  j:=r;
  xx:=len[(l+r) div 2];
  repeat
  while len[i]<xx do
  inc(i);
  while xx<len[j] do
  dec(j);
  if i<=j then
  begin
    yy:=len[i];
    len[i]:=len[j];
    len[j]:=yy;
    yy:=x[i];
    x[i]:=x[j];
    x[j]:=yy;
    yy:=y[i];
    y[i]:=y[j];
    y[j]:=yy;
    inc(i);
    dec(j);
  end;
  until i>j;
  if l<j then
  sort(l,j);
  if i<r then
  sort(i,r);
end;
begin
  readln(n,m);
  for i:=1 to m do
  readln(x[i],y[i],len[i]);
  for i:=1 to n do
  c[i]:=i;
  sort(1,m);
  for i:=1 to m do//Kruskal算法求最小生成树,并用邻接表存储
  if c[go(x[i])]<>c[go(y[i])] then
  begin
    c[go(x[i])]:=c[go(y[i])];
    inc(k);
    //因是无向图,所以每条边两点做起点的情况都不能放过
    a[k]:=len[i];//a数组装该边长度
    a[k+n-1]:=len[i];
    be[k]:=x[i];//be数组装该边起点
    be[k+n-1]:=y[i];
    tar[k]:=y[i];//tar数组装该边终点
    tar[k+n-1]:=x[i];
    next[k]:=last[x[i]];//next数组装下一个该找哪条边
    next[k+n-1]:=last[y[i]];
    last[x[i]]:=k;//last数组装以该点为起点的最后一条边是哪个
    last[y[i]]:=k+n-1;
  end;
  for j:=1 to n do//各种建树,这里使用的是BFS
  if not b[j] then
  begin
    l:=1;
    r:=1;
    find[1]:=j;//find为BFS数组
    floor[j]:=1;//floor为该点深度(根节点深度为1)
    b[j]:=true;//b数组表示是否被访问过
    while l<=r do//开始广搜
    begin
      i:=last[find[l]];
      while i>0 do
      begin
        if not b[tar[i]] then
        begin
          floor[tar[i]]:=floor[be[i]]+1;
          f[tar[i],0]:=be[i];
          s[tar[i],0]:=a[i];
          inc(r);
          find[r]:=tar[i];
          b[tar[i]]:=true;
        end;
        i:=next[i];
      end;
      inc(l);
    end;
  end;
  for j:=1 to 17 do//倍增预处理
  for i:=1 to n do
  begin
    f[i,j]:=f[f[i,j-1],j-1];
    s[i,j]:=max(s[i,j-1],s[f[i,j-1],j-1]);
  end;
  readln(m);
  for i:=1 to m do//开始查询
  begin
    readln(l,r);
    ans:=0;
    if c[go(l)]<>c[go(r)] then//特判两点不在同一集合的情况
    begin
      writeln('impossible');
      continue;
    end;
    //下面开始LCA
    if floor[l]<floor[r] then
    begin
      j:=l;
      l:=r;
      r:=j;
    end;
    if floor[l]>floor[r] then
    for j:=17 downto 0 do
    if floor[f[l,j]]>=floor[r] then
    begin
      if ans<s[l,j] then
      ans:=s[l,j];
      l:=f[l,j];
    end;
    if l=r then
    begin
      writeln(ans);
      continue;
    end;
    for j:=17 downto 0 do
    if f[l,j]<>f[r,j] then
    begin
      if ans<s[l,j] then
      ans:=s[l,j];
      if ans<s[r,j] then
      ans:=s[r,j];
      l:=f[l,j];
      r:=f[r,j];
    end;
    if ans<s[l,0] then
    ans:=s[l,0];
    if ans<s[r,0] then
    ans:=s[r,0];
    writeln(ans);
  end;
end.

 

转载于:https://www.cnblogs.com/qbwhtc/p/7406607.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值