NOIP2009最优贸易——史上最详细解析

这里写图片描述
这里写图片描述
[解题思路]
本题很直观的就是想找到一个价格最低和一个价格最高点(满足由起点能到达,且又可顺序到达终点),找最低价格的点可以这样来处理,从源点开始找到所有点的最低价格(某个顶点的最低价格就是从源点到该所有可能路径上的价格最低的点),最后枚举卖出的点,并且判断该点是否可以到达即可。此外,由于数据规模较大,一般的邻接矩阵难以承受,因此采用动态数据结构邻接表。但是本题有环,处理时还有几个细节问题:
1.由于是最后要判定所有的顶点是否可以到达终点,因此不妨将所有的边反向,由终点出发判断终点可以到达那些顶点就可以了,并做上标记。这样也就要求在读入边的时候必须反向再储存一次。
2.用SPFA求某个顶点的最低价格时,对SPFA进行了改进。由SPFA的原理可以知道,该算法可以处理环的问题,但是要求最短路径的长度是可以收敛的,也就是说不能在图中存在负权。该题目满足此要求。SPFA是用来求最短路径的算法,在此对其改进,来求路径上最小价格的点
[参考程序]

program trade;
var   belong,pri,time,maxp,minp,de:array[0..200001]of longint;
 pre,head,v,ipre,ihead,iv:array[0..500001]of longint;
 q:array[0..100000]of longint;
 vis:array[0..100000]of boolean;
 itot,tot,i,j,k,n,m,t,now,ans:longint;
procedure add(x,y:longint);    //这是用邻接表(数组方式)来存储这个图
begin                         //关于邻接表,可以点击代码下方的连接既有详细解释
    inc(tot);   //tot表示第几条路  给每条边一个编号
    v[tot]:=y;  //v[i]是编号为i的边的目的地
    pre[tot]:=head[x];  //pre[i]表示编号为i的点的前一条边的编号
    head[x]:=tot;  //head[x]是起点为x的边的最后一个编号(因为每次有新的都更新)
end;
procedure iadd(x,y:longint);  //和上面一样不用解释了吧
begin
    inc(itot);
    iv[itot]:=y;
    ipre[itot]:=ihead[x];
    ihead[x]:=itot;
end;

function max(a,b:longint):longint;  //求大值
begin if a>b then exit(a) else exit(b); end;

function min(a,b:longint):longint;  //求小值
begin if a<b then exit(a) else exit(b);end;

procedure init;
var i,x,y,z:longint;
begin
   readln(n,m);  //n个城市m条路
   for i:=1 to n do
       read(pri[i]); //pri[i]即price价格
   for i:=1 to m do
     begin
        readln(x,y,z);  //起点终点有向无向
        add(x,y);   //第一个邻接表存图
        iadd(y,x);  //第二个邻接表存反向图
        if z=2 then  begin add(y,x);iadd(x,y);end;  //2说明是两个方向要反着再来一次
     end; 
end;
procedure spfa1;  //正着一遍找小值
var l,r,i,j,k:longint;
begin
     for i:=2 to n do minp[i]:=maxlongint;  //minp[i]是能到第i个点的最小值 初始化要放大值(为什么他不放每个点的原本的价格QAQ)
     minp[1]:=pri[1];  //从1点开始搜1当然能到自己
     q[1]:=1;vis[1]:=true;  //q[i]是队列 存顶点 vis[i]是访问标记
     l:=0;r:=1;  //l队头 r队尾
     while l<>r do
         begin
             inc(l);  
             i:=head[q[l]]; //i是关于队头顶点的一条边的编号(刚刚的tot)
             vis[q[l]]:=false;  //标记顶点
             while i<>0 do  //i=0时就说明这个顶点开始的所有边已经遍历完
                begin
                    if minp[v[i]]>minp[q[l]] then  //v[i]是这条边的终点
                       begin
                           minp[v[i]]:=minp[q[l]];
                           if not vis[v[i]] then  //如果这个点不在队里就入队
                              begin
                                 vis[v[i]]:=true;
                                 inc(r);
                                 q[r]:=v[i];
                              end;
                       end;
                    i:=pre[i];//继续上一条边
                end;
         end;
    for i:= 1 to n do
       if minp[i]<>maxlongint then minp[i]:=min(pri[i],minp[i]);  //因为他不放原本价格所以这次要再比一次
end;
procedure spfa2; //反着一边找小值
var l,r,i,j,k:longint;
begin
     for i:=1 to n do vis[i]:=false;
     q[1]:=n;vis[n]:=true;
     maxp[n]:=pri[n];
     l:=0;r:=1;
     while l<>r do
         begin
             inc(l);
             maxp[q[l]]:=max(maxp[q[l]],pri[q[l]]);
             i:=ihead[q[l]]; vis[q[l]]:=false;
             while i<>0 do
                begin
                    if maxp[iv[i]]<maxp[q[l]] then
                       begin
                           maxp[iv[i]]:=maxp[q[l]];
                           if not vis[iv[i]] then
                              begin
                                 vis[iv[i]]:=true;
                                 inc(r);
                                 q[r]:=iv[i];
                              end;
                       end;
                    i:=ipre[i];
                end;
         end;
end;
begin
    init;
    spfa1;
    spfa2;
    for i:=1 to n do if maxp[i]-minp[i]>ans then ans:=maxp[i]-minp[i];  //依次枚举每个城市求最大差价
    writeln(ans);
end.

数据结构之邻接表

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值