单源最短路径(二)

单源最短路的综合应用

(1) 新年好

第一眼,时间复杂度用朴素dijkstra肯定爆炸,只能堆优化版了

这题不是吃甜草的牛那道,每个亲戚可能都不在一条路径上,要找到分支的和,想到找到最短路中最远的一个亲戚,然后不断pre到他的前一个亲戚知道1,如果没有pre到说明不在一条路径上,需要找到没有pre到的点

[!WARNING]

这样想就偏离题意了

#include<bits/stdc++.h>

using namespace std;
typedef pair<int,int> PII;
int n,m;
const int N=1e5+10;
int h[N],e[N],ne[N],w[N],idx;
int st[N],d[N],pre[N];
int vis[5];

void add(int a,int b,int c){
  e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

void dijkstra(){
  memset(d,0x3f,sizeof d);
  
  priority_queue<PII,vector<PII>,greater<PII> > q;
  q.push({0,1});
  d[1]=0;
  
  while(q.size()){
    auto t=q.top();
    q.pop();
    
    int ver=t.second,dist=t.first;
    if(st[ver]) continue;
    st[ver]=1;
    
    for(int i=h[ver];i!=-1;i=ne[i]){
      int j=e[i];
      if(d[j]>dist+w[i]){
        d[j]=dist+w[i];
        pre[j]=ver;
        q.push({d[j],j});
        cout<<ver<<' '<<j<<' '<<d[j]<<endl;
      }
    }
  }
}

signed main(){
  cin>>n>>m;
  int a[5];
  memset(h,-1,sizeof h);
  for(int i=0;i<5;i++) cin>>a[i];
  for(int i=0;i<m;i++){
    int x,y,z;
    cin>>x>>y>>z;
    add(x,y,z);
    add(y,x,z);
  }
  dijkstra();
  int ans=0;
  int end=1;
  //vis[end]=1;
  for(int i=0;i<5;i++){
    int maxn=a[i];
    for(int j=0;j<5;j++){
      if(!vis[a[j]]&&d[a[j]]>d[maxn]){
        maxn=a[j];
      }
    }
    
    cout<<maxn<<' '<<d[maxn]<<endl;
    if(!vis[maxn]){
      end=maxn;
      ans+=d[end];
      //vis[maxn]=1;
    } 
    while(end!=1&&!vis[end]){
      //cout<<end<<' ';
      vis[end]=1;
      end=pre[end]; 
    }
    //cout<<endl;
  }
  
  
  cout<<ans<<endl;
  return 0;
}

仔细分析代码和样例发现,这题和信使(最远最短路)以及牛吃甜草(选一个点到各个点最短距离只和最小)不一样

重新捋一遍思路,发现需要找到一个最优访问亲戚的顺序,那就需要对亲戚的访问顺序做一遍排列

#include<bits/stdc++.h>

using namespace std;
typedef pair<int,int> PII;
int n,m;
//是双向的,M别开小了
const int M=2e5+10,N=5e4+10,INF=0x3f3f3f3f;
int h[M],e[M],ne[M],w[M],idx;
int d[6][N],st[N],vis[N];
int a[6];

void add(int a,int b,int c){
  e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

int dfs(int u,int start,int distance){
  if(u==6){
    return distance;
  }
  int res=INF;
  for(int i=1;i<=5;i++){
    if(!vis[i]){
      int next=a[i];
      vis[i]=1;
      res=min(res,dfs(u+1,i,distance+d[start][next]));
      vis[i]=0;
    }
  }
  return res;
}

//这里d[]是局部一位数组
void dijkstra(int start,int d[]){
  
  memset(st,0,sizeof st);
  memset(d,0x3f,N*4);
  
  priority_queue<PII,vector<PII>,greater<PII> > q;
  
  q.push({0,start});
  d[start]=0;
  
  while(q.size()){
    auto t=q.top();
    q.pop();
    
    int ver = t.second,dist=t.first;
    
    if(st[ver]) continue;
    st[ver]=1;
    
    for(int i=h[ver];i!=-1;i=ne[i]){
      int j=e[i];
      if(d[j]>dist+w[i]){
        d[j]=dist+w[i];
        q.push({d[j],j});
      }
    }
  }
}

signed main(){
  cin>>n>>m;
  
  memset(h,-1,sizeof h);
  a[0]=1;
  for(int i=1;i<=5;i++) cin>>a[i];
  
  for(int i=0;i<m;i++){
    int x,y,z;
    cin>>x>>y>>z;
    add(x,y,z);
    add(y,x,z);
  }
  for(int i=0;i<6;i++) dijkstra(a[i],d[i]);
  
  cout<<dfs(1,0,0)<<endl;
  return 0;
}
(2) 通信线路

“指定路径上(1—>N)不超过 𝐾 条电缆”有点bellman_ford的味道 (✅)

“最贵” 先把最难走的路径(1—>N)给公司,然后从剩下的路径中选(❌)

但其实不是bellman_ford算法,因为不是要求在k内抵达N

#include<bits/stdc++.h>

using namespace std;
int n,p,k;
const int N=1e3+10,M=2e4+10;
int h[M],e[M],ne[M],w[M],idx,st[N],d[N];

void add(int a,int b,int c){
  e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

int check(int mid){
  memset(d,0x3f,sizeof d);
  memset(st,0,sizeof st);
  deque<int> q;
  
  q.push_back(1);
  d[1]=0;
  
  while(q.size()){
    int t=q.front();
    q.pop_front();
    
    if(st[t]) continue;
    st[t]=1;
    
    for(int i=h[t];i!=-1;i=ne[i]){
      int j=e[i],v=w[i]>mid;
      if(d[j]>d[t]+v){
        d[j]=d[t]+v;
        if(!v) q.push_front(j);
        else {
          q.push_back(j);
        }
      }
    }
  }
  return d[n]<=k;
}


signed main(){
  cin>>n>>p>>k;
  memset(h,-1,sizeof h);
  for(int i=0;i<p;i++){
    int x,y,z;
    cin>>x>>y>>z;
    add(x,y,z);
    add(y,x,z);
  }
 	
  int l=0,r=1e6+1;
  while(l<r){
    int mid=(l+r)>>1;
    if(check(mid)) r=mid;
    else l=mid+1;
  }
  if(r==1e6+1) r=-1;
  cout<<r<<endl;
  return 0;
}
(3)道路与航线

初看题目,有负边权,那么先不考虑dijkstra。

有两条道路,一种是是可以为负数的航线,一种是只有正数的道路。

“保证如果有一条航线可以从 Ai𝐴𝑖 到 Bi𝐵𝑖,那么保证不可能通过一些道路和航线从 𝐵𝑖 回到 𝐴𝑖”这句话应该是保证数据不可能出现负环。

感觉只能spfa

手搓一个spfa,第一次做这个题,没有ac

#include<bits/stdc++.h>

using namespace std;
int t,r,p,s;
const int N=2e5+10,M=3e5+10,INF=0x3f3f3f3f;
int h[M],e[M],ne[M],w[M],idx;
int st[N],d[N];

void add(int a,int b,int c){
  e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

void spfa(){
  memset(d,0x3f,sizeof d);
  
  queue<int> q;
  d[s]=0;
  q.push(s);
  
  while(q.size()){
    int t=q.front();
    q.pop();
    
    st[t]=1;
    for(int i=h[t];i!=-1;i=ne[i]){
      int j=e[i];
      if(d[j]>d[t]+w[i]){
        d[j]=d[t]+w[i];
        if(!st[j]){
          q.push(j);
        }
      }
    }
  }
}

signed main(){
  cin>>t>>r>>p>>s;
  memset(h,-1,sizeof h);
  for(int i=0;i<r;i++){
    int x,y,z;
    cin>>x>>y>>z;
    add(x,y,z);
    add(y,x,z);
  }
  
  for(int i=0;i<p;i++){
    int x,y,z;
    cin>>x>>y>>z;
    add(x,y,z);
  }
  spfa();
  for(int i=1;i<=t;i++){
    if(d[i]>=INF/2){
      puts("NO PATH");
    }
    else cout<<d[i]<<endl;
  }

  return 0;
}

ac代码

道路连起来的团看成一个拓扑点,在点内做dijkstra,同时完成拓扑。

求连通块这部分不是很熟悉

连通块代码练习

#include<bits/stdc++.h>

using namespace std;
const int N=1e2+10;
int h[N],e[N],w[N],ne[N],idx;
int bcnt,id[N];
vector<int> block[N];

void add(int a,int b,int c){
  e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

void dfs(int u){
  //把相邻的点加入同一连通块中
  block[bcnt].push_back(u);
  //记录每个点所在连通块
  id[u]=bcnt;
  
  for(int i=h[u];i!=-1;i=ne[i]){
    int j=e[i];
    if(!id[j]){
      dfs(j);
    }
  }
}

signed main(){
  int n,m;
  cin>>n>>m;
  memset(h,-1,sizeof h);
  for(int i=0;i<m;i++){
    int a,b,c;
    cin>>a>>b>>c;
    add(a,b,c);
  }
  for(int i=1;i<=n;i++){
    if(!id[i]){
      bcnt++;
      dfs(i);
      
    }
  }
  cout<<bcnt<<endl;
  for(int i=1;i<=bcnt;i++){
      cout<<i<<endl;
    for(int j=0;j<block[i].size();j++){
      cout<<block[i][j]<<' ';
    }
    cout<<endl;
  }
	return 0;
}

测试

输入:

6 4
1 3 2 
3 5 2
2 4 1
2 6 1

输出:

2
1
1 3 5 
2
2 6 4 
#include<bits/stdc++.h>

using namespace std;
int t,r,p,s;
//M别开小了
const int N=1e5+10,M=2e5+10,INF=0x3f3f3f3f;
typedef pair<int,int> PII;
int id[N],bcnt,din[N];
vector<int> block[N];
queue<int> q;
int h[M],e[M],w[M],ne[M],idx;
int st[N],d[N];

void add(int a,int b,int c){
  e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}


//用一个全局变量bcnt记录连通块个数,通过在main函数里判断是否已经被记录来自增
void dfs(int u){
  block[bcnt].push_back(u);
  id[u]=bcnt;
  
  for(int i=h[u];i!=-1;i=ne[i]){
    int j=e[i];
    if(!id[j]){
      dfs(j);
    }
  }
}

void dijkstra(int bid){
  
  priority_queue<PII,vector<PII>, greater<PII> > heap;
  
  for(int i=0;i<block[bid].size();i++){
    int j=block[bid][i];
    heap.push({d[j],j});
  }
  
  while(heap.size()){
    auto t=heap.top();
    heap.pop();
    
    int ver=t.second,dist=t.first;
    
    if(st[ver]) continue;
    st[ver]=1;
    for(int i=h[ver];i!=-1;i=ne[i]){
      int j=e[i];
      if(id[ver]!=id[j]&&--din[id[j]]==0) q.push(id[j]);
      if(d[j]>dist+w[i]){
        d[j]=dist+w[i];
        if(id[ver]==id[j])
        	heap.push({d[j],j});
      }
    }
  }
  
}

void topesort(){
  memset(d,0x3f,sizeof d);
  d[s]=0;
  //把入度为零的点加入队列中
 	for(int i=1;i<=bcnt;i++){
    if(din[i]==0){
      q.push(i);
    }
    while(q.size()){
      int t = q.front();
      q.pop();
      dijkstra(t);
    }
  }
}

signed main(){
  cin>>t>>r>>p>>s;
  memset(h,-1,sizeof h);
  for(int i=0;i<r;i++){
    int a,b,c;
    cin>>a>>b>>c;
    add(a,b,c);
    add(b,a,c);
  }
  
  for(int i=1;i<=t;i++){
    if(!id[i]){
      bcnt++;
      dfs(i);
    }
  }
  
  for(int i=0;i<p;i++){
    int a,b,c;
    cin>>a>>b>>c;
    add(a,b,c);
    din[id[b]]++;
  }
  topesort();
  for(int i=1;i<=t;i++){
    if(d[i]>=INF/2) puts("NO PATH");
    else cout<<d[i]<<endl;
  }
  return 0;
}
(4) 最优贸易

最短路计数了解一下

fhq-treap了解一下

反向建图懂了

动态规划和最短路的交集是拓扑关系

不能用dijkstra是因为最小价格(最大价格)是在一条边的两端取,会出现后出现的点可以更新前面的点的情况,所以不能用基于贪心的Dijkstra算法

#include<bits/stdc++.h>

using namespace std;
int n,m;
const int N=1e5+10,M=1e6+10;
int hs[N],ht[N],e[M],ne[M],idx;
int st[N],dmax[N],dmin[N],w[N];

void add(int *h,int a,int b){
  e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void spfa(int *d,int start,int *h,bool flag){
  queue<int> q;
  memset(st,0,sizeof st);
  if(flag){
    memset(dmin,0x3f,sizeof dmin);  //引用可以直接对原数组操作,不能对应用用sizeof
  }
  d[start]=w[start];
  q.push(start);
  
  while(q.size()){
    int t=q.front();
    q.pop();
    
    st[t]=false;
    for(int i=h[t];i!=-1;i=ne[i]){
      int j=e[i];
      if(flag&&d[j]>min(d[t],w[j])||!flag&&d[j]<max(d[t],w[j])){
        if(flag){
          d[j]=min(d[j],w[j]);
        }
        else{
          d[j]=max(d[t],w[j]);
        }
        if(!st[j]){
          q.push(j);
          st[j]=1;
        }
      }
    }
  }
}

signed main(){
  cin>>n>>m;
  memset(hs,-1,sizeof hs);
  memset(ht,-1,sizeof ht);
  for(int i=1;i<=n;i++) cin>>w[i];
  for(int i=0;i<m;i++){
    int a,b,c;
    cin>>a>>b>>c;
    add(hs,a,b);add(ht,b,a);
    if(c==2){
      add(hs,b,a);
      add(ht,a,b);
    }
  }
  spfa(dmin,1,hs,1);
  spfa(dmax,n,ht,0);
  
  int res=0;
  for(int i=1;i<=n;i++){
    res=max(dmax[i]-dmin[i],res);
  }
  
  cout<<res<<endl;
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值