zoukankan      html  css  js  c++  java
  • Luogu P1967 货车运输(NOIP2013Day1T3)

    题目描述

    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。

    Solution

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

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

    我们敏锐的发现,这题似乎与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~)

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

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

    pascal代码如下:

    uses math;
    var n,m,i,j,k,l,r,ans:longint;
    a,be,tar,next,x,y,len:array[1..50000]of longint;
    f,s:array[0..10000,0..14]of longint;
    b:array[1..10000]of boolean;
    c,last,floor,find:array[0..10000]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 14 do//倍增预处理
      for i:=1 to n do
      begin
        f[i,j]:=f[f[i,j-1],j-1];
        s[i,j]:=min(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:=maxlongint;
        if c[go(l)]<>c[go(r)] then//特判两点不在同一集合的情况
        begin
          writeln('-1');
          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:=14 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:=14 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.
  • 相关阅读:
    windows下使用vscode编写运行以及调试C/C++
    nginx基础模块
    Windows下配置nginx+php(wnmp)
    快速创建 Vue 项目
    你真的会玩SQL吗?冷落的Top和Apply
    你真的会玩SQL吗?透视转换的艺术
    你真的会玩SQL吗?你所不知道的 数据聚合
    你真的会玩SQL吗?简单的数据修改
    你真的会玩SQL吗?表表达式,排名函数
    你真的会玩SQL吗?Case也疯狂
  • 原文地址:https://www.cnblogs.com/qbwhtc/p/7411238.html
Copyright © 2011-2022 走看看