说是研究其实并没有研究只是记录一下吧……
最近状态一直不好十分颓废除了看题跑再就是一个小时懵逼地打个暴力还骗不到分,大概是应该卸了辣鸡阴阳师吧。然后做NOI2007第一题就是个最短路径计数然后我压根没好好想随便打了个
O(n4)
的SPFA暴力也不管了,第二题Po姐讲过什么Splay维护凸包貌似还可以CDQ然而都不会写,题答一个点玩了一个小时还有一个特殊点没打出来。就是这么颓废,感觉自己药丸了。
后来一想T1就是个Floyd啊,我还枚举删哪个点判断经过这个点的最短路径数,其实求出来总路径数后,对于
i,j,k
来说,如果
dist[i][j]+dist[j][k]==dist[i][k]
,那么从
i
到
然后按照原来的理解,做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
以内的点通过若干点或直接连接的最短路,所以说直接用乘法原理加法原理统计即可,若
少颓少颓少颓……
附点丑的不要不要的代码(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,稳!