单源最短路的扩展应用

文章讨论了解决多起点唯一终点的最短路问题,介绍了使用SPFA和Dijkstra算法的方法,涉及二维空间中的门、钥匙动态规划,以及求解最短路计数和观光问题的技巧,如双端BFS和二进制表示关键状态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1137. 选择最佳线路(活动 - AcWing

思路:这题仔细看一下实际就是多起点唯一终点的最短路,之前bfs中遇到过一次,这种题的最好办法就是增设一个虚拟原点,不过不用像聘礼那道题一样把虚拟原点表示出来,我们只要最开始将所有起点的距离都赋成0,并入队即可。

这道题spfa和堆优化版的dijkstra应该都可以。

#include<bits/stdc++.h>
using namespace std;
const int N=1010,M=40010;
int n,m,s,x;
int h[N],e[M],ne[M],w[M],idx;
int id[N],d[N],st[N];
void add(int a,int b,int c)
{
    w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int spfa()
{
    queue<int>q;
    memset(d,0x3f,sizeof d);
    for(int i=1;i<=x;i++)
    {
        q.push(id[i]);
        d[id[i]]=0;
    }
    while(q.size())
    {
        auto t=q.front();
        q.pop();
        st[t]=0;
        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])
                {
                    st[j]=1;
                    q.push(j);
                }
            }
        }
    }
    if(d[s]==0x3f3f3f3f) return -1;
    else return d[s];
}
int main()
{
    while(~scanf("%d%d%d",&n,&m,&s))
    {
        memset(h,-1,sizeof h);
        for(int i=1;i<=m;i++)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
        }
        scanf("%d",&x);
        for(int i=1;i<=x;i++) scanf("%d",&id[i]);
        cout<<spfa()<<endl;
    }
}

 1131. 拯救大兵瑞恩(活动 - AcWing)

思路:这道题有一个很关键的地方就是有门的存在,要开一扇门的前提是有钥匙,所以我们在走到门对应的格子的时候还需要考虑是否有钥匙。而且要使不止一把,不同的门需要用不同的钥匙来开。如果我们想随时随地知道我们当前有哪些钥匙的话,最好的办法就是单独开一维来表示手中钥匙的状态。这里我们可以借鉴dp的思想来考虑,当前点能进行哪些更新,首先如果所在的位置存在一个钥匙,那么就可以从没有这把钥匙的状态变成拥有这把钥匙的状态,然后我们还可以向上下左右四个方向延伸。那么可以由一个点更新的点的状态就都表示出来了。然后就要来想具体的实现过程:

首先要解决的第一个问题就是这里的我们只给出了有门和有墙的状态,剩下的那些可以直接到的点之间的关系需要我们手动来建,这里建边的时候,门位置可以在输入的时候就建边,墙位置不能建边,所以要判断两点之间是否是墙,可以将所有的墙都记录下来,直接连通的位置也是要建边的,但是需要与门区别,我们将边权定义为0.

其次,给出的都是二维的坐标,并不利于我们建边,因为这道题虽然是用dp来分析,但还是用图论来解决的,显然一维的点更容易建边,所以我们可以写一个映射。

还有,这道题求的是时间花费,只有移动是花时间的,所以我们拾钥匙的操作花费的时间是0,移动花费的时间是1,所以我们实际上可以通过双端bfs来实现。

以及,我们还需要记录每个位置是否有钥匙,有什么类型的钥匙,这个可以用一个数组key[]来实现,至于钥匙的种类,可以用二进制表示状态。

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
const int N=120,M=360,P=1<<11;
int n,m,p;
int h[N],e[M],ne[M],w[M],idx;
int k,s;
int key[N];
int g[12][12];
set<pair<int,int>>wall;
void add(int a,int b,int c)
{
    w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int dx[]={1,-1,0,0};
int dy[]={0,0,1,-1};
void build()
{
    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<1||x>n||y<1||y>m) continue;
                int a=g[i][j],b=g[x][y];
                if(wall.count({a,b})) continue;
                add(a,b,0);
            }
        }
    }
}
int d[N][P];
int st[N][P];
int bfs()
{
    deque<pair<int,int>>q;
    memset(d,0x3f,sizeof d);
    q.push_front({1,0});
    d[1][0]=0;
    while(q.size())
    {
        auto t=q.front();
        q.pop_front();
        if(st[t.x][t.y]) continue;
        st[t.x][t.y]=1;
        //cout<<t.x<<" "<<t.y<<endl;
        if(t.x==n*m) return d[t.x][t.y];
        if(key[t.x])
        {
            int sta=t.y|key[t.x];
            if(d[t.x][sta]>d[t.x][t.y])
            {
                d[t.x][sta]=d[t.x][t.y];
                q.push_front({t.x,sta});
            }
        }
        for(int i=h[t.x];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(w[i]&&!(t.y>>w[i]-1&1)) continue;
            if(d[j][t.y]>d[t.x][t.y]+1)
            {
                d[j][t.y]=d[t.x][t.y]+1;
                q.push_back({j,t.y});
            }
        }
    }
    return -1;
}
int main()
{
    scanf("%d%d%d",&n,&m,&p);
    int cnt=1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            g[i][j]=cnt++;
    scanf("%d",&k);
    memset(h,-1,sizeof h);
    for(int i=1;i<=k;i++)
    {
        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];
        wall.insert({a,b}),wall.insert({b,a});
        if(c)add(a,b,c),add(b,a,c);
    }
    build();
    scanf("%d",&s);
    for(int i=1;i<=s;i++)
    {
        int x,y,c;
        scanf("%d%d%d",&x,&y,&c);
        int j=g[x][y];
        key[j] |= 1<<c-1;
    }
    cout<<bfs();
}

ps:这里记录一下发现的一个有趣的东西,st[][],if(st[])是真。

1134. 最短路计数(活动 - AcWing

思路:这里要求的是最短路的条数,我们联想到dp中的方案数来解决这道题。在最短路中我们是用一个点去更新其他点,显然如果i能更新j,那么cnt[j]=cnt[i],如果从i到j恰好是已经确定的最短路, 那么应该加上从i传递来的方案数。这题边权是1,那么就用bfs,线性的时间复杂度。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=4e5+10,mod=100003;
int n,m;
int h[N],e[M],ne[M],idx;
int d[N],cnt[N];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void bfs()
{
    queue<int>q;
    q.push(1);
    memset(d,0x3f,sizeof d);
    cnt[1]=1;
    d[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(d[j]>d[t]+1)
            {
                d[j]=d[t]+1;
                cnt[j]=cnt[t];
                q.push(j);
            }
            else if(d[j]==d[t]+1)
            {
                cnt[j]=(cnt[j]+cnt[t])%mod;
            }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    for(int i=1;i<=m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b),add(b,a);
    }
    bfs();
    for(int i=1;i<=n;i++) printf("%d\n",cnt[i]);
}

 383. 观光(383. 观光 - AcWing题库)

这题和上题大差不差,只是不能用bfs了而已,同样要保证当一个点第一次出队的时候就是最小距离,那么这里使用dijkstra(这里可以类比一下之前只能用spfa那道题,那里因为维护的是最大值最小值,所以一个点出队后还有可能被更新,所以只能用spfa).

另外这里还要把只比最短路多一的也算进去,所以我们把最短路和次短路分开统计,最后判断一下是否需要相加。

#include<bits/stdc++.h>
using namespace std;
const int N=1010,M=20010;
int n,m;
int h[N],e[M],ne[M],w[M],idx;
int s,z;
void add(int a,int b,int c)
{
    w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
struct ver{
    int id,type,d;
    bool operator >(const ver &a) const 
    {
        return d>a.d;
    }
};
int d[2][N],cnt[2][N],st[N][2];//bfs中没有st是因为,每个点只会被放入一次,因为边权都是1,一层一层往外扩展的
int dijkstra()
{
    memset(d,0x3f,sizeof d);
    memset(st,0,sizeof st);
    memset(cnt,0,sizeof cnt);
    priority_queue<ver,vector<ver>,greater<ver>>q;
    q.push({s,0,0});
    d[0][s]=0,cnt[0][s]=1;
    while(q.size())
    {
        auto t=q.top();
        q.pop();
        int v=t.id,type=t.type,dist=t.d;
        if(st[v][type]) continue;
        st[v][type]=1;
        for(int i=h[v];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(d[0][j]>dist+w[i])
            {
                d[1][j]=d[0][j],cnt[1][j]=cnt[0][j];
                q.push({j,1,d[1][j]});
                d[0][j]=dist+w[i],cnt[0][j]=cnt[type][v];
                q.push({j,0,d[0][j]});
            }
            else if(d[0][j]==dist+w[i])
            {
                cnt[0][j]+=cnt[type][v];
            }
            else if(d[1][j]>dist+w[i])
            {
                d[1][j]=dist+w[i],cnt[1][j]=cnt[type][v];
                q.push({j,1,d[1][j]});
            }
            else if(d[1][j]==dist+w[i])
            {
                cnt[1][j]+=cnt[type][v];
            }
        }
    }
    if(d[0][z]+1==d[1][z]) return cnt[1][z]+cnt[0][z];
    else return cnt[0][z];
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        memset(h,-1,sizeof h);
        idx=0;
        while(m--)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
        }
        scanf("%d%d",&s,&z);
        cout<<dijkstra()<<endl;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值