牛客刷题之图论-最短路

简介: 牛客刷题之图论-最短路

1.Sightseeing Trip

思路借鉴:传送门

代码参考于:传送门

设环的形式是:i<->k<->j , i<-->j (i,j,k不同) 
floyd是典型的插点算法,每次插入点k,为此,在点k被[插入前]可计算i-j-k这个环
即此时中间节点为:1~k-1,即我们已经算出了任意i<->j的最短道路,中间经过的节点可以为 (1,2,3,...,k-1)
我们只需枚举所有以k为环中最大节点的环即可。
pos[i][j]:i~j的最短路中经过的点是k(即由这个状态转移过来),且这个k是此路径中编号最大的点(除i,j)//根据Floyd算法实质决定
这条道路存在以下两条性质
1.在i~j的最短道路中,一定没有环(显然)
2.设i,j之间的最短道路经过点k(不同于i,j),则i~k , k~j之间必然没有交集
2证:
如果有交集,设交点为k'(k' < k,根据Floyd算法实质相关),则存在道路:
i<->k'<->j , 由于[i<->k'] < [i<->k] , [j->k'] < [j->k] 
显然这条道路更小,和假设矛盾所以一定没有交集
对于pos[i][j],如果pos[i][j] == 0 : 说明i~j的最短路没有经过其他节点
因此借用性质2来求解道路,注意书写顺序,确保最后输出顺序正确
每次把i <-> j 之间划分成 i<->k , k<->j
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=110,INF=0x3f3f3f3f;
int n,m;
int pos[N][N],d[N][N],g[N][N];
int path[N],cnt;
int res=INF;
void get_path(int i,int j)//获取i~j中由哪个点转移过来的,i~k,k~j这样子
{
    int k=pos[i][j];
    if(!k) return;//假如没有
    //这里遍历的顺序随意,因为有Special judge
    get_path(i,k);
    path[cnt++]=k;
    get_path(k,j);
}
void floyd()
{
    for(int k=1;k<=n;k++)//dp思路, 假设k是环上的最大点, i ~ k ~ j(Floyd的思想)
    {
        for(int i=1;i<k;i++)// 至少包含三个点,i,j,k不重合
           for(int j=i+1;j<k;j++)
              if((ll)d[i][j]+g[i][k]+g[k][j]<res)
              {
                  res=d[i][j]+g[i][k]+g[k][j];//更新环的最小值
                  cnt=0;
                  //把路径上的点存下来,这里随便顺序,因为有Special judge
                  path[cnt++]=i;
                  path[cnt++]=k;
                  get_path(i,j);
                  path[cnt++]=j;
              }
        for(int i=1;i<=n;i++)//正常的Floyd更新最短距离
            for(int j=1;j<=n;j++)
               if(d[i][j]>d[i][k]+d[k][j])
                {
                    d[i][j]=d[i][k]+d[k][j];
                    pos[i][j]=k;//记录由那个点更新过来的
                }
    }
}
int main()
{
    memset(d,0x3f,sizeof d);
    scanf("%d%d",&n,&m);
    int a,b,c;
    while(m--)
    {
        scanf("%d%d%d",&a,&b,&c);
        d[a][b]=d[b][a]=min(d[a][b],c);
    }
    memcpy(g,d,sizeof d);
    floyd();//跑一遍Floyd算法求路径最小环
    if(res==INF) puts("No solution");//假如无环
    else
    {
        for(int i=0;i<cnt;i++) printf("%d ",path[i]);
        puts("");
    }
    return 0;
}

2.拯救大兵瑞恩

题意就是从左上角到右下角的最短距离,但是跟走迷宫不太一样,他有门的限制,所以要先开门得拿到相应的钥匙才行,走一步的消耗是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.架设电话线

看看别人的题解:传送门

二分价格最贵的那根线的费用,然后进行判断是否满足所选出的边小于等于K


#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10,M=4e3+10;
int h[N],ne[M],e[M],w[M],idx;
int n,p,k;
bool st[N];
int dist[N];
deque<int> q;
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool bfs(int mid)
{
    //清空上一层状态
    q.clear();
    memset(st,0,sizeof st);
    memset(dist,0x3f,sizeof dist);
    q.push_back(1);
    dist[1]=0;
    while(q.size())
    {
        int t=q.front();
        q.pop_front();
        if(st[t]) continue;
        st[t]=true;
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i],d=w[i]>mid;//d是判断这条边是否大于mid
            if(dist[j]>dist[t]+d)//假如可以更新最少的边
            {
                dist[j]=dist[t]+d;
                if(d) q.push_back(j);//假如是0则假如队头
                else q.push_front(j);//反之加到队尾
            }
        }
    }
    return dist[n]<=k;//看看所需的边是否小于等于k条
}
void solve()
{
    memset(h,-1,sizeof h);
    cin>>n>>p>>k;
    int a,b,c;
    while(p--)
    {
        cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    int l=0,r=1000001;
    while(l<r)//二分费用
    {
        int mid=l+r>>1;
        if(bfs(mid)) r=mid;
        else l=mid+1;
    }
    if(l==1000001) puts("-1");
    else cout<<l<<endl;
}
int main()
{
    int T=1;
    while(T--) solve();
    return 0;
}

4.农场派对

这题就是正向求一遍最短路和逆向求一遍最短路,然后更新每头牛的最大值

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10,M=2e5+10;
int h1[N],ne1[M],e1[M],w1[M],idx1;
int h2[N],ne2[M],e2[M],w2[M],idx2;
int n,m,x;
bool st[N];
int dist1[N],dist2[N];
int q[N];
void add1(int a,int b,int c)
{
    e1[idx1]=b,w1[idx1]=c,ne1[idx1]=h1[a],h1[a]=idx1++;
}
void add2(int a,int b,int c)
{
    e2[idx2]=b,w2[idx2]=c,ne2[idx2]=h2[a],h2[a]=idx2++;
}
void spfa(int h[],int e[],int w[],int ne[],int dist[],int type)//spfa求最短路
{
    memset(st,0,sizeof st);
    if(type==1)  memset(dist,0x3f,sizeof dist1);
    else  memset(dist,0x3f,sizeof dist2);
    int hh=0,tt=0;
    q[tt++]=x;
    dist[x]=0;
    while(hh!=tt)
    {
        int t=q[hh++];
        st[t]=false;
        if(hh==N) hh=0;
        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;
                    st[j]=true;
                    if(tt==N) tt=0;
                }
            }
        }
    }
}
void solve()
{
    memset(h1,-1,sizeof h1);
    memset(h2,-1,sizeof h2);
    cin>>n>>m>>x;
    int a,b,c;
    while(m--)
    {
        cin>>a>>b>>c;
        add1(a,b,c);
        add2(b,a,c);
    }
    spfa(h1,e1,w1,ne1,dist1,1);//正向求一遍最短路
    spfa(h2,e2,w2,ne2,dist2,2);//逆向求一遍最短路
    int res=0;
    for(int i=1;i<=n;i++) res=max(res,dist1[i]+dist2[i]);//求每头牛来回的最大值
    cout<<res<<endl;
}
int main()
{
    int T=1;
    while(T--) solve();
    return 0;
}

5.Roadblocks

这题求次短路,在求最短路的时候顺便更新最短路即可


1.假如最短路可以更新,则次短路等于最短路的值,最短路更新为最小的值

2.假如次短路可以更新, 并且最短路是小于更新的值的,则更新次短路的值

#include<bits/stdc++.h>
using namespace std;
const int N=5e3+10,M=2e5+10;
int h[N],e[M],ne[M],w[M],idx;
int d1[N],d2[N];//d1是最短路,d2是次短路
int n,m;
typedef struct
{
    int u,dist;
}pii;
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void bfs()
{
    memset(d1,0x3f,sizeof d1);
    memset(d2,0x3f,sizeof d2);
    queue<pii> q;
    d1[1]=0;
    q.push({1,0});
    while(q.size())
    {
        pii t=q.front();
        q.pop();
        int u=t.u,dist=t.dist;
        for(int i=h[u];~i;i=ne[i])
        {
            int j=e[i];
            if(d1[j]>dist+w[i])//假如可以更新最短路
            {
                d2[j]=d1[j];//次短路等于最短路
                d1[j]=dist+w[i];//最短路更新为最小值
                q.push({j,d1[j]});
            }
            else if(d1[j]<dist+w[i]&&d2[j]>dist+w[i])//假如可以更新次短路,并且最短路严格小于更新的值
            {
                d2[j]=dist+w[i];//更新次短路
                q.push({j,d2[j]});
            }
        }
    }
}
int main()
{
    memset(h,-1,sizeof h);
    scanf("%d%d",&n,&m);
    int a,b,c;
    while(m--)
    {
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c),add(b,a,c);
    }
    bfs();
    printf("%d\n",d2[n]);//输出次短
    return 0;
}

6.最短路计数

假如在更新最短路时,某个点j可以由某个点t更新过来,则j这个点的最短路个数cnt[j]=cnt[t],假如最短路的长度相等,则直接cnt[j]+=cnt[t].

#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条
    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;
}

7.新年好

先两两求一遍最短路,求一个地方到另一个地方的最短路,在枚举5个拜访的顺序

由于有5个亲戚,先预处理出来5个亲戚到其他所有点的最短距离待会可以直接用,则可以dfs的全排列方式来进行拜访顺序,在把经过的点加上相应的距离即可,最后更新一遍最小值

#include<bits/stdc++.h>
using namespace std;
const int N=5e4+10,M=2e5+10;
int n,m;
int h[N],e[M],w[M],ne[M],idx;
bool st[N];
int ans=0x3f3f3f3f;
int dist[10][N];
int q[N];
int id[10];//存每个亲戚所在的站点
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void spfa(int u,int dist[])//spfa求最短路
{
    memset(st,0,sizeof st);
    int hh=0,tt=0;
    q[tt++]=u;
    dist[u]=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;
                }
            }
        }
    }
}
void dfs(int cnt,int u,int sum)//枚举到u这个点,cnt个数,最短花费为sum
{
    if(sum>=ans) return;//加入大于最短了
    if(cnt==6)//加入枚举完5个亲戚了
    {
        ans=sum;
        return;
    }
    for(int i=2;i<=6;i++)//全排列枚举5个亲戚
        if(!st[i])
        {
            st[i]=true;
            dfs(cnt+1,i,sum+dist[u][id[i]]);
            st[i]=false;
        }
}
void solve()
{
    memset(h,-1,sizeof h);
    scanf("%d%d",&n,&m);
    id[1]=1;//自己在1站点
    for(int i=2;i<=6;i++) scanf("%d",&id[i]);
    memset(dist,0x3f,sizeof dist);
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c),add(b,a,c);
    }
    for(int i=1;i<=6;i++) spfa(id[i],dist[i]);//求每个亲戚到其他点的最短距离
    memset(st,0,sizeof st);
    dfs(1,1,0);//枚举搜索顺序,全排列
    printf("%d\n",ans);
}
int main()
{
     int T=1;
     while(T--) solve();
    return 0;
}

8.最优贸易

DP+spfa求最短路

dmin[i]表示1~i中的最小价值,dmax[i]表示i~n中的最大价值

则我们可以在前i个最小值入手,在后i个抛售,则获利为dmax[i]-dmin[i],然后求一遍最大即可

这题因为存在环不可用dijkstra来做

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=1e6+10;
int hs[N],ht[N],e[M],ne[M],w[N],idx;
int dmax[N],dmin[N];
int n,m;
int q[N];
bool st[N];
void add(int h[],int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void spfa(int h[],int type)
{
    int hh=0,tt=1;
    if(type)//求最大值的情况
    {
        memset(dmax,-0x3f,sizeof dmax);
        q[0]=n;
        dmin[n]=w[n];
    }
    else//求最小值的情况
    {
        memset(dmin,0x3f,sizeof dmin);
        q[0]=1;
        dmax[1]=w[1];
    }
    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(type&&dmax[j]<max(dmax[t],w[j])||!type&&dmin[j]>min(dmin[t],w[j]))//假如最大值或者最小值可以更新
            {
                if(type) dmax[j]=max(dmax[t],w[j]);//假如最大值则更新最大值
                else dmin[j]=min(dmin[t],w[j]);//反之更新最小值
                if(!st[t])
                {
                    q[tt++]=j;
                    if(tt==N) tt=0;
                    st[j]=true;
                }
            }
        }
    }
}
int main()
{
    memset(hs,-1,sizeof hs);
    memset(ht,-1,sizeof ht);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    int a,b,c;
    while(m--)
    {
        scanf("%d%d%d",&a,&b,&c);
        add(hs,a,b),add(ht,b,a);//建一条正向的供求1~i中的最小用,建一条反向的供n~i中的求最大用
        if(c==2) add(hs,b,a),add(ht,a,b);//假如是双向边,则两个都建
    }
    spfa(hs,0);//求1~i中的最小值
    spfa(ht,1);//求n~i中的最大值
    int res=0;
    for(int i=1;i<=n;i++) res=max(res,dmax[i]-dmin[i]);//求一遍最大值
    printf("%d\n",res);
    return 0;
}

9.汽车加油行驶问题

借鉴于:传送门

这题是假的网络流,虽然说费用流也不是不可以写。。。

设disx,y,kdisx,y,k表示到达位置在(x,y)(x,y),剩余油量为kk这个状态的最小代价。可知这个东西可以用SPFASPFA转移。


所以转移即可。

注意:题目中说经过油库时若油箱不满是强制加油的。没判这个就要WA两个点(而且跑出来答案小了)

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,K,A,B,C;
int mp[N][N];
int dist[N][N][11];//表示走到x,y这个点剩余步数是k的最小花费
bool st[N][N][11];
int dx[4]={0,-1,0,1},dy[4]={-1,0,1,0};//左上右下
typedef struct
{
    int x,y,k;
}pii;
queue<pii> q;
void spfa()
{
    memset(dist,0x3f,sizeof dist);
    dist[1][1][K]=0,q.push({1,1,K});//把起点放进队列中
    while(q.size())
    {
        pii t=q.front();
        q.pop();
        int x=t.x,y=t.y,k=t.k;
        st[x][y][k]=false;
        if(mp[x][y]&&k<K)//假如是加油站而且油量不够满
        {
            if(dist[x][y][K]>dist[x][y][k]+A)//假如可以加油
            {
                dist[x][y][K]=dist[x][y][k]+A;
                if(!st[x][y][K]) q.push({x,y,K}),st[x][y][K]=true;
            }
            continue;//表示加油站强制加油,则后面就不用更新了
        }
        else
        {
            if(dist[x][y][K]>dist[x][y][k]+A+C)//表示新设立一个加油站,并加油
            {
                dist[x][y][K]=dist[x][y][k]+A+C;
                if(!st[x][y][K]) q.push({x,y,K}),st[x][y][K]=true;
            }
        }
        if(k)//假如有油,则可以更新四个方向的最小值
        {
           for(int i=0;i<4;i++)
           {
               int tx=x+dx[i],ty=y+dy[i];
               if(x<1||x>n||y<1||y>n) continue;//越界
               if(i<=1&&dist[tx][ty][k-1]>dist[x][y][k]+B)//假如倒着走,则得加上费用B,而且能更新最小值
               {
                    dist[tx][ty][k-1]=dist[x][y][k]+B;
                    if(!st[tx][ty][k-1]) q.push({tx,ty,k-1}),st[tx][ty][k-1]=true;
               }
               if(i>1&&dist[tx][ty][k-1]>dist[x][y][k])//正着走,不用加费用,而且能更新最小值
               {
                   dist[tx][ty][k-1]=dist[x][y][k];
                   if(!st[tx][ty][k-1]) q.push({tx,ty,k-1}),st[tx][ty][k-1]=true;
               }
           }
        }
    }
}
int main()
{
    scanf("%d%d%d%d%d",&n,&K,&A,&B,&C);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
           scanf("%d",&mp[i][j]);
    spfa();//跑一遍spfa求最小花费
    int ans=dist[n][n][0];
    for(int i=1;i<=K;i++) ans=min(ans,dist[n][n][i]);//更新最后一个点剩余i步是最小花费
    printf("%d\n",ans);
    return 0;
}

10.道路和航线

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> pii;
const int N=25010,M=150010;
int h[N],e[M],ne[M],w[M],idx;
int n,m1,m2,s;
int id[N],bcnt,din[N];//id是由点找连通块,bcnt是连通块的个数,din表示某个连通块的入度
int dist[N];
bool st[N];
vector<int> block[N];//存某个连通块中的点
queue<int> q;
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,int ver)//当前点为u,当前连通块是ver
{
    block[ver].push_back(u);//放进连通块中
    id[u]=ver;//标记这个点属于那个连通块
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(!id[j]) dfs(j,ver);//假如没搜索过
    }
}
void dijkstra(int ver)
{
    priority_queue<pii,vector<pii>,greater<pii>> heap;
    for(int t:block[ver]) heap.push({dist[t],t});//把连通块中的所有点加入堆中
    while(heap.size())
    {
        pii t=heap.top();
        heap.pop();
        int u=t.y,d=t.x;
        if(st[u]) continue;
        st[u]=true;
        for(int i=h[u];~i;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>d+w[i])
            {
                dist[j]=d+w[i];
                if(id[j]==id[u]) heap.push({dist[j],j});//加入是同一个连通块则继续加入堆中更新
            }
            if(id[j]!=id[u]&&--din[id[j]]==0) q.push(id[j]);//加入不是一个连通块并且入度减为0了,则把这个连通块加入队列中
        }
    }
}
void topsort()
{
    memset(dist,0x3f,sizeof dist);
    dist[s]=0;
    for(int i=1;i<=n;i++)//把所有入度为0的连通块加入队列中
        if(!din[i]) q.push(i);
    while(q.size())
    {
        int t=q.front();
        q.pop();
        dijkstra(t);//连通块内部跑一遍dijkstra
    }
}
int main()
{
    memset(h,-1,sizeof h);
    scanf("%d%d%d%d",&n,&m1,&m2,&s);
    int a,b,c;
    while(m1--)//道路
    {
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c),add(b,a,c);
    }
    for(int i=1;i<=n;i++)//获取连通块,即互相能到达的点为1个连通块
        if(!id[i])//假如没有搜索过
             dfs(i,++bcnt);//开辟新的连通块
    while(m2--)
    {
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
        din[id[b]]++;//表示b这个连通块的入度++
    }
    topsort();//跑一遍拓扑序
    for(int i=1;i<=n;i++)
      if(dist[i]>=0x3f3f3f3f/2) puts("NO PATH");//假如数比较大,说明没有路径
      else printf("%d\n",dist[i]);
    return 0;
}
相关文章
牛客刷题之图论-最小生成树
牛客刷题之图论-最小生成树
88 0
|
6月前
|
存储 算法 搜索推荐
力扣每日一题 6/13 反悔贪心算法
力扣每日一题 6/13 反悔贪心算法
50 1
|
6月前
蓝桥杯动态规划每日一题
蓝桥杯动态规划每日一题
|
安全 算法
【洛谷刷题】蓝桥杯专题突破-广度优先搜索-bfs(16)
【洛谷刷题】蓝桥杯专题突破-广度优先搜索-bfs(16)
118 0
|
存储 算法
【洛谷刷题】蓝桥杯专题突破-广度优先搜索-bfs(14)
【洛谷刷题】蓝桥杯专题突破-广度优先搜索-bfs(14)
108 0
|
机器学习/深度学习 算法 定位技术
【洛谷刷题】蓝桥杯专题突破-广度优先搜索-bfs(15)
【洛谷刷题】蓝桥杯专题突破-广度优先搜索-bfs(15)
113 0
|
7月前
|
算法
六六力扣刷题贪心算法之基础和最大子序和
六六力扣刷题贪心算法之基础和最大子序和
45 0
|
存储 算法 搜索推荐
蓝桥杯丨分治算法
蓝桥杯丨分治算法
74 0
|
测试技术 Python
蓝桥杯丨动态规划
蓝桥杯丨动态规划
64 0
|
机器学习/深度学习 算法
蓝桥杯丨回溯法
蓝桥杯丨回溯法
51 0