图论(3)单源最短路拓展

活动 - AcWing

acw1137

如果我们确定了起点,那么就是一个模板题

选起点有两个办法:1.选取虚拟原点,连接家附近的所有车站。直接以虚拟原点作为我起点跑最短路即可。2.反向建图,取终点到家附近所有车站的dist,取min即可。 

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N =1010,M=20010,INF=0x3f3f3f3f;

int n,m,T;
int h[N],e[M],ne[M],w[M],idx;
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++;
}

void spfa()
{
    int scnt;
    cin>>scnt;
    memset(dist,0x3f,sizeof dist);
    int hh=0,tt=0;
    while(scnt--)
    {
        int u;
        cin>>u;
        dist[u]=0;
        q[tt++]=u;
        st[u]=true;
    }

    while(hh!=tt)
    {
        int t=q[hh++];
        if(hh==N) hh=0;

        st[t]=false;
        for(int i=h[t];i!=-1;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; 
                }

            }
        }
    }
}


int main()
{
    while(scanf("%d%d%d",&n,&m,&T)!=-1)
    {
        memset(h,-1,sizeof h);
        idx=0;
        while(m--)
        {
            int a,b,c;
            cin>>a>>b>>c;
            add(a,b,c);
        }
        spfa();
        if(dist[T]==INF) dist[T]=-1;
        cout<<dist[T]<<endl;
    }
}

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4338580/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

acw1131

思路1:

来自:AcWing 1131. 拯救大兵瑞恩(普通BFS解决) - AcWing 

在直接bfs可做的基础上,引入了“钥匙”和门这个因素。可以想到暴力做法,直接在状态中加入“持有钥匙”这一维度,现在题目就转化为了从状态f[1][0]转化为f[n*m][…]状态的最小步数了。

我们走到有钥匙的格子上,并不用考虑要不要拿钥匙,拿钥匙又不会增加成本,只管拿就行。因此,转移到某个格子时,直接计算下这个格子的状态,格子上有钥匙就在之前状态基础上加上这个钥匙,没有钥匙就继承之前的钥匙状态。这样一来,问题中就不存在边权为0的边了,只要状态转移了,步长都是加一,普通的BFS就可以解决了。

细节:二维坐标压缩成一个数,这样可以g[a][b]表示a到b之间的边了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N =105,M=12;
typedef pair<int,int> PII;

int g[N][N],key[N],dist[N][1<<M];
bool st[N][1<<M];
int n,m,p,k,s;

int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};


int get(int x,int y)//坐标的状态压缩
{
    return (x-1)*m+y;
}


int bfs()
{
    queue<PII> q;
    q.push({1,key[1]});
    st[1][key[1]]=true;
    memset(dist,0x3f,sizeof dist);
    dist[1][key[1]]=0;
    
    while(q.size())
    {
        auto t=q.front();
        q.pop();
        int ver=t.first,state=t.second;
        int x=(ver-1)/m+1,y=(ver-1)%m+1;
        
        
        for(int i=0;i<4;i++)
        {
            int a=x+dx[i],b=y+dy[i];
            int s=state,p=get(a,b);
            if(!a||!b||a>n||b>m||!g[ver][p]) continue;
            if(g[ver][p]!=-1)
                if(!(s>>g[ver][p]&1)) continue;
            
            s|=key[p];
            if(!st[p][s])
            {
                q.push({p,s});
                st[p][s]=true;
                dist[p][s]=dist[ver][state]+1;
            }
            if(p==n*m) return dist[p][s];
            
        }
    }
    return -1;
}


int main()
{
    cin>>n>>m>>p;
    cin>>k;
    memset(g,-1,sizeof g);
    for(int i=0;i<k;i++)
    {
        int x1,y1,x2,y2,z;
        cin>>x1>>y1>>x2>>y2>>z;
        int p1=get(x1,y1),p2=get(x2,y2);
        g[p1][p2]=g[p2][p1]=z;
    }
    
    cin>>s;
    for(int i=0;i<s;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        key[get(x,y)] |= 1<<z;
    }
    cout<<bfs()<<endl;
}

思路2:

 

 

把捡钥匙看做一次移动,边权为0。格子间的移动视为一次移动,边权是1,然后使用双端队列bfs。线性复杂度。

小结:

思考图论问题有时候和dp十分相似。思考问题前,我们可以先把图假设为拓扑图,然后思考可行的dp方式,如状态表示和状态转移。

但是实际上图是可能存在环的,因此直接dp这种线性的方式不可行,但是可以借助图论算法框架来处理。某些图论算法还具有拓扑图性质。后续还会数次讲到类似思想。 

acwing1134最短路计数

首先用dp的方式思思考一下:

如果我们要计算i点的最短路方案,集合可以按照从哪个点转移到i点来划分。状态计算也非常简单,,,设i点可以由k1,k2,,kn点转移过去。则状态转移方程为:if(dist(ki)+w(i)==dist(i) cnt(i)+=cnt(ki)。  

为此,我们要知道所有ki点的最短路dist。我们发现一个性质,当我们要求状态state的时,状态集合里的所有值我们需要事先知道。这就是一个拓扑性质。要在计算最短路时统计方案数,则要求具备拓扑性质(最短路树),那么跑哪个算法满足我们的要求呢?

 抽象成拓扑图:跑一遍可以跑出最短路树,无环。判断方式:求解每个点的dist不互相依赖。

BFS 只入队一次,出队一次。可以抽象成拓扑图, 因为它可以保证被更新的点的父节点一定已经是最短距离了,并且这个点的条数已经被完全更新过了。这个性质是核心性质。
dijkstra 每个点只出队一次。也可以抽象成拓扑图, 同理由于每一个出队的点一定已经是最短距离,并且它出队的时候是队列中距离最小的点,这就代表他的最短距离条数已经被完全更新了,所以构成拓扑性质。
bellman_ford算法 spfa 本身不具备拓扑序,因为更新它的点不一定是最短距离,所以会出错。

当然spfa是可以做的,不过需要先把最短路,再统计方案。 

#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;

const int N =100010,M=400010,mod=100003;

int h[N],e[M],ne[M],idx;
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);
    queue<int> q;
    q.push(1);

    cnt[1]=1;
    dist[1]=0;

    while(q.size())
    {
        int t=q.front();
        q.pop();

        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[t]+1)
            {
                dist[j]=dist[t]+1;
                q.push(j);
                cnt[j]=cnt[t];
            }
            else if(dist[j]==dist[t]+1)
            {
                cnt[j]=(cnt[j]+cnt[t])%mod;
            }

        }
    }

}


int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    for(int i=0;i<m;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b),add(b,a);
    }

    bfs();
    for(int i=1;i<=n;i++) cout<<cnt[i]<<endl;
    return 0;


}

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4340742/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

acwing383 单源次最短路

 本题就是上一题的拓展,即统计次短路条数。如果次短路比最短路多1,则答案加上次短路条数。

同理可知,求次短路,bfs和dijkstra仍然具有拓扑性质。因为不可能存在环形依赖,一个点不可能反回去更新前一个点的次短路(因为这样次短路就会变长,显然是不可能的)。因此可以边算最短路、次短路,边统计最短路和次短路的方案。

结合第2道题,我们需要加入一维状态表示最短,次短。

状态转移:初始状态:最短路为0,没有次短路。最短路方案为1,没有次短路方案。

计算:如果新路dist比最短路小,最短路更新放入堆中继续搜,原最短路沦为次短路,次短路被更新,放入堆中继续搜。

如果与最短一样大,统计方案

如果比次短小,比最短大,更新次短

如果和次短一样大,收钱。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;

const int N =1010,M=20010;

struct Ver{
    int id,type,dist;
    bool operator> (const Ver &W) const
    {
        return dist > W.dist;
    }
};
int h[N],e[M],ne[M],w[M],idx;
int n,m,S,T;
int cnt[N][2],dist[N][2];
bool st[N][2];

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

int dijkstra()
{
    memset(cnt,0,sizeof cnt);
    memset(st,false,sizeof st);
    memset(dist,0x3f,sizeof dist);

    dist[S][0]=0,cnt[S][0]=1;
    priority_queue<Ver, vector<Ver>, greater<Ver> > heap;
    heap.push({S,0,0});

    while(heap.size())
    {
        auto t=heap.top();
        heap.pop();
        int ver=t.id,type=t.type,distance=t.dist,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][1]});
                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][1]==dist[T][0]+1) res+=cnt[T][1];

    return res;
}


int main()
{
    int cases;
    cin>>cases;
    while(cases--)
    {
        scanf("%d%d",&n,&m);
        memset(h,-1,sizeof h);
        idx=0;

        while(m--)
        {
            int a,b,c;
            cin>>a>>b>>c;
            add(a,b,c);
        }
        scanf("%d%d",&S,&T);
        cout<<dijkstra()<<endl;
    }
    return 0;
}


作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4372015/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值