【算法提高课】图论:单源最短路的综合应用

Acwing.342 道路与航线

  • 题意: 给定 T ( 2 e 4 ) T(2e4) T(2e4) 个点 R ( 5 e 4 ) R(5e4) R(5e4) 条道路和 P ( 5 e 4 ) P(5e4) P(5e4) 条航线,其中边权绝对值大小为 C ( 1 e 4 ) C(1e4) C(1e4) ,道路为双向边,边权恒为正,航线为单向边,边权可能为负。图中保证无环,求起点到其他所有点的单源最短路。
  • 思路: 做的时候感觉一眼SPFA,结果是可想而知的:最后两个点会被卡掉。考虑将只有道路的图分为一个个联通块,因为边权为正,所以可以在连通块里面做 Dijkstra,在连通块之间做拓扑排序,然后按照拓扑序做每一个连通块的 Dijkstra 。
  • 为什么这样做是正确的: Dijkstra 基于贪心,在有负权边的图里失效。先做连通块里正确的 Dijkstra 然后失效的部分用拓扑排序修复:遇到负权边的时候能正确更新但是不用加入堆。那为什么按照拓扑序又是正确的呢? 因为每次入队也就是允许访问的节点的入度为0,这保证我们到此点的所有路径已经被走过并且在此过程中不断更新最短距离,当我们开始访问入度为0的点时,已经没有其他到此点的路径去更新它的最短距离,这就修复了 Dijkstra。
  • C o d e Code Code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
const int INF=0x3f3f3f3f;
typedef pair<int,int> pii;
int dis[N],to[N],head[N],nxt[N],w[N],idx,d[N],gs[N];
bool st[N];
int n,A,B,start,bcnt;
vector<int>block[N];
queue<int>q;

void add(int u,int v,int val){
   to[++idx]=v,w[idx]=val,nxt[idx]=head[u],head[u]=idx;
}

void dfs(int u,int bcnt){
   gs[u]=bcnt;
   block[bcnt].push_back(u);
   
   for(int i=head[u];i;i=nxt[i]){
       int j=to[i];
       if(!gs[j])  dfs(j,bcnt);
   }
}

void dijkstra(int id){
   priority_queue<pii,vector<pii>,greater<pii>>heap;
   
   for(auto t:block[id])
       heap.push({dis[t],t});
       
   while(heap.size()){
       auto t=heap.top();
       heap.pop();
       int cur=t.second;
       
       if(st[cur]) continue;
       st[cur]=true;
       
       for(int i=head[cur];i;i=nxt[i]){
           int j=to[i];
           
           
           if(gs[j]!=gs[cur]){
                   d[gs[j]]--;
                   if(!d[gs[j]])   q.push(gs[j]);
               }  
           
           if(dis[j]>dis[cur]+w[i]){
               dis[j]=dis[cur]+w[i];
               
               if(gs[cur]!=gs[j])   continue;
               heap.push({dis[j],j});
           }

       }
   }
   
}

void topsort(){
   memset(dis,0x3f,sizeof dis);
   dis[start]=0;
   for(int i=1;i<=bcnt;i++)
       if(d[i]==0) q.push(i);
   
   while(q.size()){
       int t=q.front();
       q.pop();
       
       dijkstra(t);
       
   }
   
}

int main(){
   cin>>n>>A>>B>>start;
   for(int i=1;i<=A;i++){
       int u,v,w;
       cin>>u>>v>>w;
       add(u,v,w);
       add(v,u,w);
   }
   
   for(int i=1;i<=n;i++)
       if(!gs[i]){
           bcnt++;
           dfs(i,bcnt);
       }  
   
   for(int i=1;i<=B;i++){
       int u,v,w;
       cin>>u>>v>>w;
       add(u,v,w);
       d[gs[v]]++;
   }
   
   topsort();
   
   for(int i=1;i<=n;i++){
       if(dis[i]>=INF/2)   puts("NO PATH");
       else    cout<<dis[i]<<endl;
   }
   
   return 0;
}

Acwing.341 最优贸易

  • 题意: 给定 n ( 1 e 5 ) n(1e5) n(1e5) 个点和 m ( 5 e 5 ) m(5e5) m(5e5) 条边,一定会从 1 走到 n ,每一个点的商品售价不同,在给定路线下需要做最优的低买高卖,即卖出值减买入值最大。
  • 思路: 采用分割的思路,dmin[i]和dmax[i]1到i买入的最小值和i到n卖出的最大值,之前的最短路维护的都是和,现在是维护最大值和最小值,考虑 Dijkstra 和 SPFA,其中 Dijkstra 可以举出反例证明是错误的:如果当前 dmin[i] 最小的点是 5,那么有可能存在边 5-> 6, 6-> 7, 7-> 5,假设当前 dmin[5] = 10,则有可能存在 6 的价格是11, 但 7 的价格是3,那么 dmin[5] 的值就应该被更新成3,因此当前最小值也不一定是最终最小值,所以dijkstra算法并不适用,我们只能采用 spfa 算法。 y总提到SPFA在大部分情况下是不会失效的。
  • 优化代码: 建立两个不同的图只需要head数组不同即可,可以用函数封装 SPFA 和 add 简化。
  • C o d e Code Code (需要注意SPFA维护最大最小值的写法)
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
const int INF=0x3f3f3f3f;
int to[N],headA[N],nxt[N],headB[N],idx;
int n,m,res,dmin[N],dmax[N];
int w[N];
bool st[N];

void add(int head[],int u,int v){
   to[++idx]=v,nxt[idx]=head[u],head[u]=idx;
}

void spfa(int head[],int d[],bool flag){
   if(flag)    memset(dmin,0x3f,sizeof dmin);
   memset(st,false,sizeof st);
   queue<int>q;
   if(flag)    q.push(1),st[1]=true,d[1]=w[1];
   else    q.push(n),st[n]=true,d[n]=w[n];
   
   while(q.size()){
       int t=q.front();
       q.pop();
       st[t]=false;
       
       for(int i=head[t];i;i=nxt[i]){
           int j=to[i];
           if((flag&&d[j]>min(d[t],w[j]))||(!flag&&d[j]<min(d[t],w[j]))){
               if(flag) d[j]=min(d[t],w[j]);
               else    d[j]=max(d[t],w[j]);
               if(st[j])   continue;
               st[j]=true;
               q.push(j);
           }
       }
   }
   
}

int main(){
   cin>>n>>m;
   for(int i=1;i<=n;i++)   cin>>w[i];
   for(int i=1;i<=m;i++){
       int a,b,c;
       cin>>a>>b>>c;
       if(c==1)    add(headA,a,b),add(headB,b,a);
       else    add(headB,a,b),add(headB,b,a),add(headA,a,b),add(headA,b,a);
   }
   
   spfa(headA,dmin,true);
   spfa(headB,dmax,false);
   
   for(int i=1;i<=n;i++)
       res=max(res,dmax[i]-dmin[i]);
   
   cout<<res;
   
   return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值