CF#722 (Div2) It‘s a bird! No, it‘s a plane! No, it‘s AaParsa! 详细题解

题目大意:

给定一张有向图。每条边可以有这样的操作:如果一条边a->b=c,即从a指向b,边权为c。那么有a->(b+1)%n=c+1,即可以从a指向(b+1)%n=c+1。即输入的图只是初始状态,每条边每间隔1s都会指向不同的终点。要求在这张图上任意两点的最短距离。

思路:

此题唯一不容易处理的是“等待”这个操作,那么先考虑不等待的情况,

If(Graph[i][j]){   //I与j相连
	To=d[i]+j //即走到这条边时,终点的位置
	d[to]=min(d[to],d[i]+Graph[i][j]);  //用dijkstra的方法进行拓展
}

那么如果有等待操作呢?
在这里插入图片描述
假设0到1有一条权值位2的边,那么下一秒,就有一条指向2的权值为3的边,依此类推。并且对于每一个点实时保存这些信息会MLE和TLE的。所以不能将这些边真的保存下来再访问(例如分层图和DP都不行)。那么需要将情况进行简化,不能每一个点都要等待N秒。即如果0与1有一边,那么同时更新d[1]的值和d[2]的值,d[2]=min(d[2],d[1]+1),意味着如果已经得到d[1]的最小值,那么在到达1之前等待1s就可以到2了。

那么这样的简化会不会漏掉情况,剩下的0-3,0-4都没有更新啊。其实这只是暂时的,当到达2时,同时也会更新3。到达3时也会更新4。那么还有一个问题,这里只讨论了0的等待,那么1如果有边也可以等待啊。我们想一想,如果达到1之后再等1s才能到达2,那么我们也可以在到达1之前等1s再到2,答案上是更优的(因为1->2的边权值是正值)。那么如果到达1之后再等2s才能到达2呢,那么这是不必要的,因为同样,我们可以在到达1之前等1s再到2,这个答案更优。所以只讨论起点会进行等待就能得到最优答案。

关于代码:

那么综上,所有情况讨论完了。再讨论一个问题,用什么跑最短路,题目给出边的最大值是 M<N2 ,那么此时如果跑优先队列优化的dijkstra就会有N*MlogN= N3 logN的时间复杂度,而普通版本是 N2 的dijkstra总时间复杂度是N^3。所以跑稠密图就不要优化了。此题不能用Floyd跑的原因在于图不是唯一确定的,而dijkstra可以实时的维护图的状态,并求出最短路。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=600+10;
long long int G[MAXN][MAXN];
long long int d[MAXN];
bool v[MAXN];
int n,m;
void dijkstra(int s){
    memset(v,0,sizeof(v));
    long long int maxx=1e12;
    for(int i=0;i<n;i++)d[i]=maxx;
    for(int i=0;i<n;i++){
        if(G[s][i]){
            d[i]=min(d[i],G[s][i]);
        }
    }
    while(1){
        int p=-1;
        maxx=1e12;
        for(int i=0;i<n;i++){
            if(!v[i]&&maxx>d[i]){
                maxx=d[i];
                p=i;
            }
        }
        if(p==-1)break;
        v[p]=1;
        d[(p+1)%n]=min(d[(p+1)%n],d[p]+1);
        for(int i=0;i<n;i++){
           if(G[p][i]){
                int r=(d[p]+i)%n;
                d[r]=min(d[r],d[p]+G[p][i]);
           }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int f,t,c;
        scanf("%d%d%d",&f,&t,&c);
        G[f][t]=c;
    }
    for(int i=0;i<n;i++){
        dijkstra(i);
        for(int j=0;j<n;j++){
            if(i==j)printf("0 ");
            else printf("%lld ",d[j]);
        }
        printf("\n");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值