1700施工方案第二季{转 仕林}

总的来说,这是一道考图论知识的题,可以搜到很多关于树的直径的东西,以及一些证明,这是一道好题!
program shigong;
type arr=array[1..200000]of longint;
var n,m,tot,{边数}
    one,{以节点1为树根时dfs得到的最远点}
    two:longint;{以one为树根时dfs得到的最远点,d(one,two)既为树的直径}
    max:int64;{最远的路径}
    x,y,w:array[1..200000]of longint;{初始边}
    f,{并查集中用作父亲节点,清零后在dfs中用来存每个点的前驱}
    d:array[1..100000]of longint;{记录每个点到树根的路径长度}
    mstx,msty,mstw:array[1..200000]of longint;{存最小生成树}
    s,t:array[1..100000]of longint;{每个节点作为有向边起点时连接的边编号范围}
    v:array[1..100000]of boolean;{标志数组避免dfs重复访问}

procedure init;
var i:longint;
begin
  read(n);read(m);
  for i:=1 to m do
    begin
      read(x[i]);read(y[i]);read(w[i]);
    end;
end;

procedure qsort(l,r:longint;var a,b,c:arr);{快排的设计很巧妙}
var i,j,x,y:longint;
begin
  i:=l;j:=r;
  x:=a[(l+r)shr 1];
  repeat
    while a[i]<x do inc(i);
    while a[j]>x do dec(j);
    if i<=j then begin
      y:=a[i];a[i]:=a[j];a[j]:=y;
      y:=b[i];b[i]:=b[j];b[j]:=y;
      y:=c[i];c[i]:=c[j];c[j]:=y;
      inc(i);dec(j);
      end;
  until i>j;
  if i<r then qsort(i,r,a,b,c);
  if j>l then qsort(l,j,a,b,c);
end;

function find(p:longint):longint;
begin
  if f[p]=p then exit(p);
  f[p]:=find(f[p]);
  exit(f[p]);
end;

procedure union(a,b:longint);
var x,y:longint;
begin
  x:=find(a);y:=find(b);
  f[x]:=y;
end;

function same(a,b:longint):boolean;
begin
  if find(a)=find(b) then exit(true);
  exit(false);
end;

procedure kruskal;
var i:longint;ans:int64;
begin
  qsort(1,m,w,x,y);{此处按权值排序}
  for i:=1 to n do f[i]:=i;
  ans:=0;tot:=0;
  for i:=1 to m do
   if not same(x[i],y[i]) then
     begin
      inc(ans,w[i]);
      union(x[i],y[i]);
      inc(tot);
      mstx[tot]:=x[i];
      msty[tot]:=y[i];
      mstw[tot]:=w[i];
      if tot=n-1 then break;
     end;
  writeln(ans);
end;

procedure prepare;
var i:longint;
begin
  for i:=1 to tot do
  begin
   mstx[n+i-1]:=msty[i];
   msty[n+i-1]:=mstx[i];
   mstw[n+i-1]:=mstw[i];
  end;
  tot:=tot*2;
  qsort(1,tot,mstx,msty,mstw);{注意:此处按始节点排序}
  s[mstx[1]]:=1;
  t[mstx[tot]]:=tot;
  for i:=1 to tot do
   begin
    if (i>1)and(mstx[i]<>mstx[i-1]) then s[mstx[i]]:=i;
    if (i<tot)and(mstx[i]<>mstx[i+1]) then t[mstx[i]]:=i;
   end;
end;

procedure dfs(p:longint);
var i:longint;
begin
  v[p]:=true;
  for i:=s[p] to t[p] do
   if not v[msty[i]] then
    begin
      d[msty[i]]:=d[p]+mstw[i];
      if max<d[msty[i]] then
      begin two:=msty[i];max:=d[msty[i]];end;
      f[msty[i]]:=p;
      dfs(msty[i]);
    end;
end;

function min(a,b:longint):longint;
begin
  if a<b then exit(a);
  exit(b);
end;

function maxx(a,b:longint):longint;
begin
  if a>b then exit(a);
  exit(b);
end;

procedure main;
var now,ans:int64;
begin
  fillchar(v,sizeof(v),false);
  max:=0;two:=1;
  dfs(mstx[1]);
  one:=two;max:=0;
  fillchar(d,sizeof(d),0);
  fillchar(v,sizeof(v),false);
  fillchar(f,sizeof(f),0);
  dfs(one);
  now:=0;ans:=maxlongint;
  while two<>one do
   begin
    ans:=min(ans,maxx(now,max-now));{寻找在树的直径上距离两端路径长度的最大     值最小的点,此节点即为所求,根据直径的定义,可以知道在直径之外的点到这       点的路径长<=这个最小的最大值,否则这条就不是直径了,所以此法正解}
    now:=now+d[two]-d[f[two]];
    two:=f[two];
   end;
  write(ans);
end;

begin
  init;
  kruskal;
  prepare;
  main;
end.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值