单源最短路的拓展应用

简介: 单源最短路的拓展应用

1.选择最佳路线

Problem - 2680 (hdu.edu.cn)

这题就是问多个起点到一个终点的最短路

1.可以直接建反向边,然后从终点跑一遍最短路,最后求一下每个起点的最短路即可


2.建虚拟原点来跑最短路,适用范围广,可以用在多个起点和多个终点的情况,建虚拟原点就是从0号点跟起点连一条边权为0的边,然后跑一遍最短路,因为这时从0号点到终点的最短路相当于从任何一个起点到终点的最短路了,答案就是dist【终点】

#include<bits/stdc++.h>
using namespace std;
const int N=1010,M=21010;
int h[N],e[M],ne[M],w[M],idx;
int n,m,T;
int dist[N],q[N];
bool st[N];
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int spfa()//spfa算法求最短路
{
    memset(dist,0x3f,sizeof dist);
    int hh=0,tt=1;
    dist[0]=0;
    q[0]=0;
    while(hh!=tt)
    {
        int t=q[hh++];
        if(hh==N) hh=0;
        st[t]=false;
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[t]+w[i])
            {
                dist[j]=dist[t]+w[i];
                if(!st[j])
                {
                    q[tt++]=j;
                    if(tt==N) tt=0;
                    st[j]=true;
                }
            }
        }
    }
    if(dist[T]==0x3f3f3f3f) return -1;
    return dist[T];
}
int main()
{
   while(~scanf("%d%d%d",&n,&m,&T))
   {
       memset(h,-1,sizeof h);
       idx=0;
       int a,b,c,s;
       while(m--)
       {
           scanf("%d%d%d",&a,&b,&c);
           add(a,b,c);
       }
       scanf("%d",&s);
       while(s--)
       {
           scanf("%d",&a);
           add(0,a,0);//建立虚拟原点0,连一条边权为0的边
       }
       printf("%d\n",spfa());
   }
    return 0;
}

2.拯救大兵瑞恩

拯救大兵瑞恩 (nowcoder.com)

题意就是从左上角到右下角的最短距离,但是跟走迷宫不太一样,他有门的限制,所以要先开门得拿到相应的钥匙才行,走一步的消耗是1点体力


我们可以采用状态分析的方式进行考虑,d[x,y,state]所有从起点走到(x,y)这个格子且拥有的钥匙的状态是state的所有路线的集合

但是由于这里可能会有环 没有拓扑序 实际不能用dp做 只能转换为最短路

把两种转移方式看成两种边


因为这里只有0和1的边所以可以用双端队列广搜来做,然后答案就是d[n,m,0~2^p-1]

然后我们可以用一维的坐标边数两维的位置

#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef pair<int,int> pii;
const int N=11,M=N*N,E=400,P=1<<10;
int h[M],e[E],ne[E],w[E],idx;//w记录的是门需要的钥匙
int n,m,p,k;
int g[N][N],key[M];
int dist[M][P];
bool st[M][P];
set<pii> edges;//用来记录出现过的边
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void built()
{
    int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
           for(int u=0;u<4;u++)//枚举四个方向
            {
                int x=i+dx[u],y=j+dy[u];
                if(x<=0||x>n||y<=0||y>m) continue;//越界
                int a=g[i][j],b=g[x][y];
                if(edges.count({a,b})==0) add(a,b,0);//假如不是门或者墙,则说明不用钥匙也能过去,即0就是不需要钥匙
            }
}
int bfs()//双端队列广搜
{
    memset(dist,0x3f,sizeof dist);
    dist[1][0]=0;//从起点走,钥匙的状态是0
    deque<pii> q;
    q.push_back({1,0});
    while(q.size())
    {
        pii t=q.front();
        q.pop_front();
        if(st[t.x][t.y]) continue;
        st[t.x][t.y]=true;
        if(t.x==n*m) return dist[t.x][t.y];//假如走到了终点直接返回
        if(key[t.x])//假如这个位置有钥匙
        {
            int state=t.y|key[t.x];//把这个钥匙的状态加上
            if(dist[t.x][state]>dist[t.x][t.y])
            {
                dist[t.x][state]=dist[t.x][t.y];//更新最短距离
                q.push_front({t.x,state});//因为边权是0,则加到队头中去
            }
        }
        for(int i=h[t.x];~i;i=ne[i])//枚举领边
        {
            int j=e[i];
            if(w[i]&&!(t.y>>w[i]-1&1)) continue;//假如这个位置有钥匙但是开不了门
            if(dist[j][t.y]>dist[t.x][t.y]+1)//更新最短距离
            {
                dist[j][t.y]=dist[t.x][t.y]+1;
                q.push_back({j,t.y});//因为边权是1则加到队尾
            }
        }
    }
    return -1;//反之无解
}
int main()
{
  scanf("%d%d%d%d",&n,&m,&p,&k);
  for(int i=1,t=1;i<=n;i++)//将二维坐标映射到一维的点中
      for(int j=1;j<=m;j++)
         g[i][j]=t++;
  memset(h,-1,sizeof h);
  while(k--)
  {
      int x1,y1,x2,y2,c;
      scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&c);
      int a=g[x1][y1],b=g[x2][y2];
      edges.insert({a,b}),edges.insert({b,a});//标记这两条边有门或者墙
      if(c) add(a,b,c),add(b,a,c);//加入是门,则这条边加上这个门需要的钥匙
  }
  built();//建立其他点之间的边权
  int s;
  scanf("%d",&s);
  while(s--)//读入钥匙
  {
      int x,y,id;
      scanf("%d%d%d",&x,&y,&id);
      key[g[x][y]]|=1<<id-1;//将这个位置的钥匙的状态存下来
  }
  printf("%d\n",bfs());
    return 0;
}

3.最短路计数

最短路计数 (nowcoder.com)

按照DP的做法来求方案数,但是满足DP的前提是有拓扑序才能做,最短路但是不满足,所以得按照下面算法来求


天生带有拓扑序的最短路的算法有:dijska和bfs,因为他们处理的时候就是按照最小进行操作,不带有环,所以不会在后面在回过来更新前面的数,所以就拥有拓扑序

所以这题可以直接用dijska来做即可,本身就具备拓扑序

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=4e5+10,mod=100003;
int h[N],e[M],ne[M],idx;
int q[N];
int n,m;
int dist[N],cnt[N];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void bfs()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    int hh=0,tt=0;
    q[0]=1;
    cnt[1]=1;//1到1的方案数是1
    while(hh<=tt)
    {
        int t=q[hh++];
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[t]+1)//假如能更新最小值
            {
                dist[j]=dist[t]+1;
                cnt[j]=cnt[t];//把方案数等于能更新我的方案数
                q[++tt]=j;
            }
            else if(dist[j]==dist[t]+1) cnt[j]=(cnt[j]+cnt[t])%mod;//假如相等,则方案数相加
        }
    }
}
int main()
{
    memset(h,-1,sizeof h);
    scanf("%d%d",&n,&m);
    int a,b;
    while(m--)
    {
        scanf("%d%d",&a,&b);
        add(a,b),add(b,a);
    }
    bfs();//跑一遍BFS
    for(int i=1;i<=n;i++) printf("%d\n",cnt[i]%mod);
    return 0;
}

4.观光

383. 观光 - AcWing题库

这题较上题需要多求一个次短路径跟次短路径的条数,做法跟上一题一样

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10,M=1e4+10;
int n,m;
int S,T;
int h[N],e[M],ne[M],w[M],idx;
int dist[N][2],cnt[N][2];
bool st[N][2];
struct Vers//自定义点,因为是大根堆所以重载大于号
{
    int ver,type,dist;
    bool operator >(const Vers &W)const
    {
        return dist>W.dist;
    }
};
void add(int a,int b,int c)
{
    w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int dijska()
{
    //清空上一层状态
    memset(st,0,sizeof st);
    memset(cnt,0,sizeof cnt);
    memset(dist,0x3f,sizeof dist);
    priority_queue<Vers,vector<Vers>,greater<Vers>> heap;//定义一个小根堆
    heap.push({S,0,0});//把起点存下来
    dist[S][0]=0,cnt[S][0]=1;
    while(heap.size())
    {
        Vers t=heap.top();
        heap.pop();
        int ver=t.ver,distance=t.dist,type=t.type,count=cnt[ver][type];
        if(st[ver][type]) continue;//假如已经遍历过
        st[ver][type]=true;
        for(int i=h[ver];~i;i=ne[i])
        {
            int j=e[i];
            if(dist[j][0]>distance+w[i])//假如可以更新最短路
            {
                dist[j][1]=dist[j][0],cnt[j][1]=cnt[j][0];//则次短路就是最短路,方案数也跟着更新
                heap.push({j,1,dist[j][0]});
                dist[j][0]=distance+w[i],cnt[j][0]=count;//更新一下最短路和方案数
                heap.push({j,0,dist[j][0]});
            }
            else if(dist[j][0]==distance+w[i]) cnt[j][0]+=count;//假如刚好相等,则直接加上方案数
            else if(dist[j][1]>distance+w[i])//假如可以更新次短路
            {
                dist[j][1]=distance+w[i],cnt[j][1]=count;//更新一下次短路和方案数
                heap.push({j,1,dist[j][1]});
            }
            else if(dist[j][1]==distance+w[i]) cnt[j][1]+=count;//假如刚好相等,则直接加上方案数
        }
    }
    int res=cnt[T][0];
    if(dist[T][0]+1==dist[T][1]) res+=cnt[T][1];//假如次短路等于最短路+1,则加上这个方案数
    return res;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        //清空
        memset(h,-1,sizeof h);
        idx=0;
        scanf("%d%d",&n,&m);
        int a,b,c;
        while(m--)
        {
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
        }
        scanf("%d%d",&S,&T);
        printf("%d\n",dijska());
    }
    return 0;
}
相关文章
|
5月前
|
人工智能 BI
树状数组及其拓展
树状数组及其拓展
31 0
|
5月前
|
C++
7 树形DP及其衍生
7 树形DP及其衍生
33 0
|
5月前
|
C++
最小生成树的拓展应用
最小生成树的拓展应用
35 0
|
5月前
|
C++
单源最短路的综合应用
单源最短路的综合应用
33 0
|
5月前
|
C++
6 区间DP及其衍生
6 区间DP及其衍生
31 0
|
5月前
|
C++
5 状态压缩Dp及其衍生
5 状态压缩Dp及其衍生
34 0
|
5月前
|
C++
8.数位DP及其衍生
8.数位DP及其衍生
40 0
|
11月前
|
算法 C++
【每日算法Day 109】五大解法,带你深入了解完全背包方案数
【每日算法Day 109】五大解法,带你深入了解完全背包方案数
|
算法 C++ SoC
算法笔记(五)——小而美的算法技巧—前缀和
算法笔记(五)——小而美的算法技巧—前缀和
算法笔记(五)——小而美的算法技巧—前缀和
|
算法 C++
<<算法很美>>——(五)——回溯算法核心框架(上)
<<算法很美>>——(五)——回溯算法核心框架(上)
<<算法很美>>——(五)——回溯算法核心框架(上)