【XJB研究】关于最短路径数

说是研究其实并没有研究只是记录一下吧……
最近状态一直不好十分颓废除了看题跑再就是一个小时懵逼地打个暴力还骗不到分,大概是应该卸了辣鸡阴阳师吧。然后做NOI2007第一题就是个最短路径计数然后我压根没好好想随便打了个 O(n4) 的SPFA暴力也不管了,第二题Po姐讲过什么Splay维护凸包貌似还可以CDQ然而都不会写,题答一个点玩了一个小时还有一个特殊点没打出来。就是这么颓废,感觉自己药丸了。
后来一想T1就是个Floyd啊,我还枚举删哪个点判断经过这个点的最短路径数,其实求出来总路径数后,对于 i,j,k 来说,如果 dist[i][j]+dist[j][k]==dist[i][k] ,那么从 i k经过 j 的最短路径数就是cnt[i][j]cnt[j][k]。我可能真的药丸了。
然后按照原来的理解,做SPFA的时候直接统计的话,如果 dist[v]>dist[u]+len ,那么直接松弛并令 cnt[v]=cnt[u] ,否则如果 dist[v]==dist[u]+len ,那么 cnt[v]+=cnt[u]
然后光荣的WA了。
这个想法其实挺显而易见的,还用它过过一道题,USACO的Lilypad Pond。那道题BFS就可以,这样做是正确的是因为相当于每条边的长度都是1,不存在松弛过程中的后效性。不过SPFA的话不对了,有边权的话不能保证每次正确的统计。
比如说下面这个图:
这是张图
从1开始做,先松弛2和3,然后2松弛了4和5,3松弛了4,4把自己的路径数累加到2上,然而5没有被累加,WA。如果说从2再做一遍呢?无法判断2所连接的点是否需要清空重新计算。
这大概就是SPFA的队列优化的局限性。
然而用Dijkstra就是对的,因为它保证了每次取距离当前节点距离最小的点,不会再有后面的点更新这个点,不会对其再次更新的点产生影响。
要是有人像我一样只会SPFA怎么办(貌似很多大佬都bsSPFA)。
那就先做朴素的SPFA,然后从终点做DFS回去,找到到这个点最短路上的点,继续DFS后累加方案数即可。
另外最短路三兄弟Floyd其实更可以求,因为它本质是一个DP,DP[i][j][k]表示 i 以内的点通过若干点或直接连接的最短路,所以说直接用乘法原理加法原理统计即可,若dist[j][k]>dist[i][j]+dist[j][k],令 cnt[j][k]=cnt[i][j]cnt[j][k] ,否则若 dist[j][k]==dist[i][j]+dist[j][k] ,则 cnt[j][k]+=cnt[i][j]cnt[j][k]
少颓少颓少颓……


附点丑的不要不要的代码(VSCode好牛逼啊,clang-format格式化完加上空格是好看了,不过还是没用它的)

void dfs(int u, int p){
    for(edge *t=g[p];t;t=t->next){
        if(dist[u][t->to]+t->len!=dist[u][p])
            continue;
        if(!cnt[u][t->to]) dfs(u, t->to);
        cnt[u][p]+=cnt[u][t->to];
    }
}
void SPFA(int u){
    // SPFA
    cnt[u][u]=1;
    for(int i=1;i<=n;i++)
        if(i!=u&&!cnt[u][i])
            dfs(u, i);
}
void dijkstra(int u){
    // ...
    while(!Q.empty()){
        w=Q.top(), Q.pop();
        if(dist[u][w.to]<w.dis) continue;
        for(edge *t=g[w.to];t;t=t->next)
            if(dist[u][t->to]>dist[u][w.to]+t->len){
                dist[u][t->to]=dist[u][w.to]+t->len;
                cnt[u][t->to]=cnt[u][w.to];
                Q.push(rec(t->to, dist[u][t->to]));
            }
            else if(dist[u][t->to]==dist[u][w.to]+t->len)
                cnt[u][t->to]+=cnt[u][w.to];
    }
}
void floyd(){
    for(int i=1;i<=n;i++)
        cnt[i][i]=1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            for(int k=1;k<=n;k++)
                if(dist[j][k]>dist[j][i]+dist[i][k]){
                    dist[j][k]=dist[j][i]+dist[i][k];
                    cnt[j][k]=cnt[j][i]*cnt[i][k];
                }
                else if(dist[j][k]==dist[j][i]+dist[i][k])
                    cnt[j][k]+=cnt[j][i]*cnt[i][k];
}

Dij,稳!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值