单源最短路的扩展应用

1.DP拆点+最短路:https://www.acwing.com/problem/content/1133/

显然,我们只知道位置是不够的,因此我们还需要拆点:令dp[i][k]表示走到了位置i(二维投射到一维)拿了钥匙k(01二进制)的最小步数。

我们先暂时不考虑图上问题,我们用刷表(DP转移的第二种形式)分析转移:

1.贪心的想,有钥匙拿就拿,不浪费步数:dp[i][k|k1]=dp[i][k]

2.正常走一步:dp[i1][k]=1+dp[i][k]

但是这里的DP关系不一定满足拓扑序,我们不妨把每一个DP抽象成空间的点,转移就是图上的走。于是问题就是求一个最短路。

同时注意到这里路的权值只有0/1,直接用双端队列即可。

这里建边也比较麻烦,我们可以这么考虑:

首先按照题目把钥匙的门连起来,然后类似BFS的我们枚举每一个格子,循环4个方向,在不出界的条件下建边即可。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second

int n,m,p;
int k;
struct node{
    int dian,id;
};
int key[11101];
vector<node> edge[100010];
int g[101][101];
set<pair<int,int> > s;
int dis[1010][2000];
int st[1010][2000];
void build(){
    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 k=0;k<4;k++){
                int x = i + dx[k], y = j + dy[k];
                if (x<=0 || x > n || y<=0 || y > m) continue;
                int a = g[i][j], b = g[x][y];
                if (!s.count({a, b})) edge[a].push_back({b,0});
            }
        }
    }
}
int bfs(){
    memset(dis,0x3f,sizeof(dis));
    dis[1][0]=0;
    deque<pair<int,int> > q;
    q.push_front({1,0});
    while(!q.empty()){
        auto ck=q.front();
        q.pop_front();
        if(st[ck.x][ck.y]) continue;
        st[ck.x][ck.y]=1;
        if (ck.x == n * m) return dis[ck.x][ck.y];
        if(key[ck.x]){
            int state = ck.y | key[ck.x];
            if(!st[ck.x][state]&&(dis[ck.x][state] > dis[ck.x][ck.y]))
            {
                dis[ck.x][state] = dis[ck.x][ck.y];
                q.push_front({ck.x, state});
            }
        }
        for(int i=0;i<edge[ck.x].size();i++){
            auto fk=edge[ck.x][i];
            if(fk.id&&((ck.y>>(fk.id-1))&1)==0) continue;
            if (dis[fk.dian][ck.y] > dis[ck.x][ck.y] + 1){
                dis[fk.dian][ck.y] = dis[ck.x][ck.y] + 1;
                q.push_back({fk.dian, ck.y});
            }
        }
    }
    return -1;
}
int main(){
    cin>>n>>m>>p>>k;
    for(int i=1,t=1;i<=n;i++){
        for(int j=1;j<=m;j++) g[i][j]=t++;
    }
    for(int i=1;i<=k;i++){
        int x1,y1,x2,y2,G;
        cin>>x1>>y1>>x2>>y2>>G;
        s.insert({g[x1][y1],g[x2][y2]});
        s.insert({g[x2][y2],g[x1][y1]});
        if(G==0) continue;
        edge[g[x1][y1]].push_back({g[x2][y2],G});
        edge[g[x2][y2]].push_back({g[x1][y1],G});
        
    }
    build();
    int y;
    cin>>y;
    while(y--){
        int x, y, c;
        cin >> x >> y >> c;
        key[g[x][y]] |= 1 << c - 1;
    }
    cout<<bfs();
}

2.最短路计数:https://www.acwing.com/problem/content/1136/

主要结论:计数顺序可以顺着BFS,迪杰斯特拉,而贝尔曼之类的需要先建立好路径转移的拓扑序树,比较麻烦。

证明就不详细展开了,最关键的就是确保第一次出了的点不会被后来的点更新。

AC代码:

#include<bits/stdc++.h>
using namespace std;
vector<int> edge[100010];
int dis[1000010];
int cnt[1000010];
int n,m;
int mod=1e5+3;
queue<int> q;
int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int x,y;
        cin>>x>>y;
        edge[x].push_back(y);
        edge[y].push_back(x);
    }
    memset(dis, 0x3f, sizeof dis);
    dis[1]=0;
    cnt[1]=1;
    q.push(1);
    while(!q.empty()){
        int ck=q.front();
        q.pop();
        for(int i=0;i<edge[ck].size();i++){
            int w=edge[ck][i];
            if(dis[w]>dis[ck]+1){
                dis[w]=dis[ck]+1;
                cnt[w]=cnt[ck];
                q.push(w);
            }
            else if(dis[w]==dis[ck]+1){
                cnt[w]=(cnt[w]+cnt[ck])%mod;
            }
        }
    }
    for(int i=1;i<=n;i++) cout<<cnt[i]<<endl;
}

3.次短路计数:https://www.acwing.com/problem/content/385/

类似的,无论是最短还是次短,优先队列弹出的一定是最终的答案,次数也不会在后面杯更新,正确性可知。

AC代码:

#include<bits/stdc++.h>
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 n, m, S, T;
int h[N], e[M], w[M], ne[M], idx;
int dist[N][2], cnt[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(st, 0, sizeof st);
    memset(dist, 0x3f, sizeof dist);
    memset(cnt, 0, sizeof cnt);
    dist[S][0] = 0, cnt[S][0] = 1;
    priority_queue<Ver, vector<Ver>, greater<Ver>> heap;
    heap.push({S, 0, 0});

    while (heap.size())
    {
        Ver 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][0] + 1 == dist[T][1]) res += cnt[T][1];
    return res;
}
int main()
{
    int cases;
    scanf("%d", &cases);
    while (cases -- )
    {
        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, &T);
        printf("%d\n", dijkstra());
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值