acm-(好题、分层图bfs)2020 China Collegiate Programming Contest - Mianyang Site E. Escape from the Island

题面
传送门
本题让求解最坏情况下的最短路,首先这个最坏情况非常难理解,我们假设 d i s [ i ] [ j ] dis[i][j] dis[i][j]表示位于点 i i i还剩 j j j步的时候在最坏情况下到达终点的最短路。

如果没有这个最坏情况,并且没有 s t e p 2 step2 step2这个转移,那么我们就相当于再无向图的分层图上跑一次 b f s bfs bfs即可。

加入 s t e p 2 step2 step2这个转移后,并且要求最坏情况,那么我们其实只能让 s t e p 2 step2 step2转移达到最坏。

那么当 j = k j=k j=k的时候,我们发现 d i s [ i ] [ j ] dis[i][j] dis[i][j]这个状态一定是由某个状态通过 s t e p 2 step2 step2到达的。而最坏情况下,我们一定要让这个 s t e p 2 step2 step2的转移最不优,也就是说,如果某个未更新状态 d i s [ a ] [ b ] dis[a][b] dis[a][b]可以通过 s t e p 2 step2 step2到达 d i s [ i ] [ k ] dis[i][k] dis[i][k]这个状态,那么一定要在没有其他选项的时候再去转移,也就是说后面再不存在一个点 j j j使得 d i s [ a ] [ b ] dis[a][b] dis[a][b]能够通过 s t e p 2 step2 step2转移到 d i s [ j ] [ k ] dis[j][k] dis[j][k],这时候如果再不转移的话,那么 d i s [ a ] [ b ] dis[a][b] dis[a][b]这个状态就无法到达终点了(这显然不符合规则,即能走到终点不管怎样都一定要走到终点),因此在这个时候转移一定能够保证 d i s [ a ] [ b ] dis[a][b] dis[a][b]在最坏情况下转移。

不过 s t e p 1 step1 step1就必须要保证最优转移,即我们在分层图上从终点 b f s bfs bfs的时候,一旦遇到某个未更新状态可以通过 s t e p 1 step1 step1转移到当前状态,那么就必定要直接更新该状态,因为当前状态是这个未更新状态遇到的第一个状态,后面遇到的状态一定不会更优(根据bfs特点,越后面值越大)。

考虑程序如何实现,首先开两个图,一个有向图(反图,因为要从终点bfs),一个无向图(代码中用g1,g2表示)。然后从先将 d i s [ n ] [ 0 ∼ k ] dis[n][0\sim k] dis[n][0k]加入队列,然后开始bfs,分为两种情况,如果当前状态是 d i s [ i ] [ k ] dis[i][k] dis[i][k],那么只考虑 s t e p 2 step2 step2的转移,为了保证转移最坏,我们记录了反图中每个点的入度,存在 d e g [ ] deg[] deg[]数组中,当 d e g [ j ] > 0 deg[j]>0 deg[j]>0的时候,若 j j j能通过 s t e p 2 step2 step2到达 i i i,首先给 d e g [ j ] deg[j] deg[j]减一,注意到 d e g [ j ] deg[j] deg[j]若不为0那么意味着 j j j点还能通过 s t e p 2 step2 step2转移到其它点,否则的话就直接转移到当前的 d i s [ i ] [ k ] dis[i][k] dis[i][k]状态即可。
如果当前状态时 d i s [ i ] [ 1 ∼ k − 1 ] dis[i][1\sim k-1] dis[i][1k1],那么就按正常的bfs更新即可,只要遇到一个未更新状态就直接更新(因为要保证最优)。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 1e5+10;
const int maxm = 1e5+10;
const int inf = 2147483647;
const ll INF = 9223372036854775807;

int n,m,k;
int dis[maxn][55],deg[maxn],vis[maxn];
vector<int>g1[maxn],g2[maxn];
struct Node{
    int u,o;
};
queue<Node>q;
void bfs(){
    for(int i=0;i<=k;++i){
        dis[n][i]=0;
        q.push(Node{n,i});
    }
    while(!q.empty()){
        int u=q.front().u,o=q.front().o;q.pop();
        if(o==k){
            for(int i=0;i<g1[u].size();++i){
                int v=g1[u][i];
                deg[v]--;
                if(!deg[v]){
                    for(int j =0;j<=k;++j){
                        if(dis[v][j]==-1){
                            dis[v][j]=dis[u][o]+1;
                            q.push(Node{v,j});
                        }
                    }
                }
            }
            if(vis[u]){
                for(int j =0;j<k;++j){
                    if(dis[u][j]==-1){
                        dis[u][j]=dis[u][k]+1;
                        q.push(Node{u,j});
                    }
                }
            }
        }else{
            for(int i =0;i<g2[u].size();++i){
                int v=g2[u][i];
                if(dis[v][o+1]==-1){
                    dis[v][o+1]=dis[u][o]+1;
                    q.push(Node{v,o+1});
                }
            }
        }
    }
}
int main(){
    int t,kase=0;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d%d",&n,&m,&k);
        for(int i=1;i<=n;++i){
            g1[i].clear(),g2[i].clear();
            deg[i]=0;
            vis[i]=0;
        }
        for(int i=0;i<m;++i){
            int u,v;
            scanf("%d%d",&u,&v);
            deg[u]++;
            g1[v].push_back(u);
            g2[u].push_back(v);
            g2[v].push_back(u);
        }
        for(int i =1;i<=n;++i){
            for(int j=0;j<=k;++j)dis[i][j]=-1;
            if(!deg[i])vis[i]=1;
        }
        bfs();
        printf("Case #%d:\n",++kase);
        for(int i =1;i<=n;++i){
            int ans=inf;
            for(int j=0;j<=k;++j)if(dis[i][j]!=-1)ans=min(ans,dis[i][j]);
            if(ans==inf)printf("-1\n");else printf("%d\n",ans);
        }
    }
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值