最短路计数类问题 —— 最短路扩展

最短路计数

传送门

题面

在这里插入图片描述
在这里插入图片描述

思路

和DP中的计数问题类似;

首先我们得先求出最小值是多少;

再求出当前这个状态能由哪几个状态转移最小值过来;

需要注意,DP问题都是满足拓扑序的,但是图论的问题不一定;

因此我们要满足更新是符合拓扑序的


最短路计数首先满足条件是不能存在值为0的环,因为存在的话那么被更新的点的条数就为 I N F INF INF了;

接着,我们需要把图抽象成最短路树,或者称为拓扑图;


而我们求最短路一般有三种方法;

  • BFS,适用于边权为1的情况;
  • 迪杰斯特拉系列(包括0-1BFS),适用于非负权图;
  • SPFA,基本都适用;

因为要满足依赖是拓扑序,我们分析一下这三类方法;

BFS,每个点只会入队出队各一次,必然是拓扑序的;

迪杰斯特拉系列,每个点只会出队一次,因此也可以抽象成拓扑序;

SPFA,每个点会多次入队出队,故天生不带有拓扑序


至于为什么,以及如果想使用SPFA怎么办呢?见下图;

在这里插入图片描述


因为这题是无权图,因此我们可以将看成边权只有 1 1 1,那我们直接写个BFS即可;

Code

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <utility>
#include <queue>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

int n,m;

struct Node{
    int to,next;
}e[N << 2];

int head[N],dist[N],tot,cnt[N];
const int MOD = 100003;
bool vis[N];
void add(int u,int v){
    ++tot;
    e[tot].next = head[u];
    e[tot].to = v;
    head[u] = tot;
}
void bfs(){
    queue<int> que;
    que.push(1);
    memset(dist,0x3f,sizeof dist);
    dist[1] = 0;
    vis[1] = 1;
    cnt[1] = 1;
    while(!que.empty()){
        auto u = que.front();
        que.pop();
        for(int i=head[u];i;i=e[i].next){
            int to = e[i].to;
            if(!vis[to] && dist[to] > dist[u] + 1){
                dist[to] = dist[u] + 1;
                cnt[to] = cnt[u];
                que.push(to);
                vis[to] = 1;
            }
            else if(dist[to] == dist[u] + 1){
                cnt[to] += cnt[u];
                cnt[to] %= MOD;
            }
        }
    }
}
void solve(){
    cin >> n >> m;
    while(m--){
        int u,v;
        cin >> u >> v;
        add(u,v),add(v,u);
    }
    bfs();
    for(int i=1;i<=n;++i) cout << cnt[i] << '\n';
}

signed main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t;
    t = 1;
    while(t--)
        solve();
    return 0;
}

观光

传送门

题面

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

思路

和单纯的求最短路条数一样,求次短路条数的时候,要想同步更新,也需要满足拓扑序(即能更新当前状态的所有状态都已经被更新好了);


上面也分析过了,这题因为边权都是正的,有两点不用考虑;

  1. 不存在权值为0的环(有这种情况就无解了,有INF种)
  2. 不用考虑负的边权,可以用拓扑序的迪杰斯特拉;

在这里插入图片描述

Code

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <vector>

using namespace std;

typedef long long ll;

const int N = 1e3 + 10,M = 1e4 + 10;

int tot,n,m,S,F;

struct Edge{
    int next,to,val;
}e[M];

int head[N],dist[N][2],cnt[N][2];
bool vis[N][2];

void add(int u,int v,int w){
    ++tot;
    e[tot].val = w;
    e[tot].next = head[u];
    e[tot].to = v;
    head[u] = tot;
}
struct Point{
    //type为0表示最短路 为1表示次短路
    int id,type,dist;
    //小根堆重载大于号
    bool operator>(const Point& p) const{
        return dist > p.dist;
    }
};
int dijkstra(){
    memset(dist,0x3f,sizeof dist);
    memset(cnt,0,sizeof cnt);
    memset(vis,0,sizeof vis);
    priority_queue<Point,vector<Point>,greater<Point>> pq;
    dist[S][0] = 0;
    cnt[S][0] = 1;
    pq.push({S,0,0});
    while(!pq.empty()){
        auto p = pq.top();
        pq.pop();
        int u = p.id,type = p.type;
        if(vis[u][type]) continue;
        vis[u][type] = 1;
        for(int i=head[u];i;i=e[i].next){
            int to = e[i].to;
            int val = e[i].val;
            if(dist[to][0] > dist[u][type] + val){
                //最小值被覆盖 那么次小值接替最小值
                dist[to][1] = dist[to][0];
                cnt[to][1] = cnt[to][0];
                pq.push({to,1,dist[to][1]});
                dist[to][0] = dist[u][type] + val;
                cnt[to][0] = cnt[u][type];
                //更新过都要放入队列(迪杰斯特拉算法)
                pq.push({to,0,dist[to][0]});
            }
            else if(dist[to][0] == dist[u][type] + val){
                cnt[to][0] += cnt[u][type];
            }
            //注意这里不能if
            else if(dist[to][1] > dist[u][type] + val){
                dist[to][1] = dist[u][type] + val;
                cnt[to][1] = cnt[u][type];
                pq.push({to,1,dist[to][1]});
            }
            else if(dist[to][1] == dist[u][type] + val){
                cnt[to][1] += cnt[u][type];
            }
        }
    }
    int ret = cnt[F][0];
    if(dist[F][0] + 1 == dist[F][1]) ret += cnt[F][1];
    return ret;
}
void solve(){
    tot = 0;
    memset(head,0,sizeof head);
    cin >> n >> m;
    for(int i=1;i<=m;++i){
        int u,v,w;
        cin >> u >> v >> w;
        add(u,v,w);
    }
    cin >> S >> F;
    cout << dijkstra() << '\n';
}

signed main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t;
    cin >> t;
    while(t--)
        solve();
    return 0;
}

逛公园

P3953 [NOIP2017 提高组] 逛公园

题面

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

思路

还没看懂…

Code

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值