bzoj 1050/ codevs 1001 贪心(伪Kruscal)

25 篇文章 0 订阅
11 篇文章 0 订阅

题意:一个n个顶点,m条边的无向图,每条边有一个权值vi,给出两个顶点S和T,找出一条S到T的路径使最大边与最小边的比最小。如果S到T没有路径输出:"IMPOSSIBLE"  否则输出最简比值

对于 impossible /是否合法判断 :并查集维护连通性,判断S和T是否在一个集合里
重点考虑要求:最大边与最小边的比最小,那么一定是 让最大边尽量小的同时让最小边尽量大
我们尝试按照一定顺序去枚举S到T之间所有合法路径的最大值和最小值的组合,ans=min(maxn/minn) 
我们借鉴kruscal的贪心方法和加边方法
那么,我们可以枚举最小边,判断以这条边为最小边是否有合法路径,如果有合法路径的话找到它对应的最大值
由于我们要时刻保持原则:让最大边尽量小的同时让最小边尽量大
所以我们在枚举最小边后,在找它对应的最大边时要使最大边尽量小,即从恰好不小于最小边的边从小到大加入边,如果s和t在一个集合里就break,最后加入的边就是最大边
那么我们找到了一条合法路径和一条尽量小的最大边但是我们枚举的这条最小边并不能保证最小边尽量大
怎么办?0.0
倒着再来一遍,从恰好不大于最大边的边从大到小加入边如果s和t在同一集合里就break,最后加入的边就是该最大边所对应的最小边
此时我们满足了我们的贪心原则:让最大边尽量小的同时让最小边尽量大
判断是否更新答案
================我是理想与现实的分界线_(:з」∠)_================
所以我们的总体思想和方法为:
1、原则:让最大边尽量小的同时让最小边尽量大
2、方法:借鉴kruscal
3、并查集维护连通性
4、先把所有边按边权从小到大排序,枚举最小边判断是否能够成合法的路径,
      如果可以找到合法路径,找到最大边并以最大边倒着来一遍找到其对应的最小边判断是否更新答案;
      如果不可以找到合法路径直接break掉
因为[i,m]区间的所有边都无法构成合法路径那么[i+1,m]、[i+2,m] ....区间的所有边也无法构成合法路径
type
        rec=record
            a,b,len:longint;
end;

var
        n,m,s,t         :longint;
        maxn,minn       :longint;
        tmax,tmin       :longint;
        i               :longint;
        l               :array[0..5010] of rec;
        f               :array[0..510] of longint;
function get_father(x:longint):Longint;
begin
   if x=f[x] then exit(x);
   f[x]:=get_Father(f[x]);
   exit(F[x]);
end;

function gcd(a,b:longint):longint;
begin
   if b=0 then exit(a) else exit(gcd(b,a mod b));
end;

procedure print;
var
        tt:longint;
begin
   tt:=gcd(maxn,minn);
   maxn:=maxn div tt;
   minn:=minn div tt;
   if (minn=1) then writeln(maxn) else writeln(maxn,'/',minn);
end;

procedure sort(ll,rr:longint);
var
        i,j:longint;
        x:longint;
        y:rec;
begin
   i:=ll;j:=rr;
   x:=l[(ll+rr)>>1].len;
   while (i<=j) do
   begin
      while (l[i].len<x) do inc(i);
      while (l[j].len>x) do dec(j);
      if (i<=j) then
      begin
         y:=l[i];l[i]:=l[j];l[j]:=y;
         inc(i);dec(j);
      end;
   end;
   if (i<rr) then sort(i,rr);
   if (j>ll) then sort(ll,j);
end;

procedure work(x:longint);
var
        ta,tb,tp:longint;
        i:longint;
begin
   for i:=1 to n do f[i]:=i;
   for i:=x to m do
   begin
      if get_father(s)=get_father(t) then break;
      ta:=get_father(l[i].a);
      tb:=get_father(l[i].b);
      if(ta<>tb) then
      begin
         tmax:=l[i].len;
         tp:=i;
         f[tb]:=ta;
      end;
   end;
   if get_father(s)<>get_father(t) then exit;
   //
   for i:=1 to n do f[i]:=i;
   for i:=tp downto 1 do
   begin
      if get_father(s)=get_father(t) then break;
      ta:=get_father(l[i].a);
      tb:=get_father(l[i].b);
      if (ta<>tb) then
      begin
         tmin:=l[i].len;
         f[tb]:=ta;
      end;
   end;
end;

begin
   read(n,m);
   for i:=1 to m do read(l[i].a,l[i].b,l[i].len);
   read(s,t);
   sort(1,m);
   maxn:=maxlongint;minn:=1;
   for i:=1 to m do
   begin
      work(i);
      if get_father(s)=get_father(t) then
      begin
         if tmax/tmin<maxn/minn then
         begin
            maxn:=tmax;
            minn:=tmin;
         end;
      end else break;
   end;
   if i=1 then writeln('IMPOSSIBLE') else print;
end.
——by Eirlys


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值