题目链接:http://bestcoder.hdu.edu.cn/contests/contest_showproblem.php?cid=863&pid=1002
小 A 是社团里的工具人,有一天他的朋友给了他一个 n 个点,m 条边的正权连通无向图,要他计算所有点两两之间的最短路。
作为一个工具人,小 A 熟练掌握着 floyd 算法,设 w[i][j] 为原图中 (i,j) 之间的权值最小的边的权值,若没有边则 w[i][j]=无穷大。特别地,若 i=j,则 w[i][j]=0。
Floyd 的 C++ 实现如下:
for(int k=1;k<=p;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
w[i][j]=min(w[i][j],w[i][k]+w[k][j]);
当 p=n 时,该代码就是我们所熟知的 floyd,然而小 A 为了让代码跑的更快点,所以想减少 p 的值。
令 Di,j 为最小的非负整数 x,满足当 p=x 时,点 i 与点 j 之间的最短路被正确计算了。
现在你需要求 ,虽然答案不会很大,但为了显得本题像个计数题,你还是需要将答案对 998244353 取模后输出。
题意:
一张无向图,n个点m条边。
As we all know, 求两点间最短路时,需要进行松弛操作
把(有效的)松弛操作过程中涉及的下标最大的点记为p,
要正确计算这两点间的最短路,p最小是多少
(如果u和v之间的最短路就是edge(u,v),则p为0(不需要松弛)其他情况 )
答案一定是 u 到 v 的最短路上(不算端点)编号最大的那个节点,根据Floyed算法流程容易证明;如果最短路有很多条呢,那么就取每个最大值的最小值;跑N遍迪杰斯特拉,不断更新Du,v即可;假设现在走 x 到 v 这条边,如果dist[u][v]被更新,那么(1)x != u 时:D[u][v] = max(D[u][x],x),(2)x == u时:D[u][v] = D[u][x](因为端点不能算)
思路:
对每个点跑一边Dijkstra,在松弛操作里操作一番。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,int>PII;//这里的路径权值忘开ll 一直WA
const int N=1005,M=4005,MOD=998244353;
int n,m;
int h[N],e[M],ne[M],idx;
ll w[M];
ll dist[N];
bool st[N];
int cnt[N][N];
ll res=0;
void add(int a, int b, ll c){
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void Dij(int s){
memset(st,0,sizeof st);
memset(dist,0x3f,sizeof dist);
dist[s]=0;
priority_queue<PII,vector<PII>,greater<PII> >heap;
heap.push({0,s});
while(heap.size()){
auto t=heap.top();
heap.pop();
int ver=t.second;
ll distance=t.first;
if(st[ver]) continue;
st[ver]=true;
for(int i=h[ver];~i;i=ne[i]){
int j=e[i];
if(w[i]+distance<dist[j]){//w[i]:ver-j边的权值; distance:s-ver最短路长
dist[j]=w[i]+distance;
heap.push({dist[j],j});
if(dist[j]==w[i]) cnt[s][j]=0;//如果s-j的最短路就是它们之间的那条边(s-j的最短路长dist[j]等于ver-j的权值 也即ver==s ) 不需要枚举
else cnt[s][j]=max(ver,cnt[s][ver]);
}else if(w[i]+distance==dist[j]){//多条最短路
heap.push({dist[j],j});//cnt更新了 还是要入队
cnt[s][j]=min(cnt[s][j],max(cnt[s][ver],ver));
}
}
}
}
int main(){
int t;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
idx=0;
memset(cnt,0,sizeof cnt);
res=0;
for(int i=0;i<m;++i){
int a,b;
ll c;
scanf("%d%d%I64d",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
for(int i=1;i<=n;++i){
Dij(i);
}
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
res=(res+cnt[i][j])%MOD;
}
}
printf("%I64d\n",res);
}
}
注意:
1. 稀疏图,用邻接矩阵会T
2. 答案是最短路上的下标的最大值,但不是某轮松弛操作的最大值,有可能那个点被其他下标更大的点更新过!想清楚最短路构成 不断更新的轮数 像递推和BFS似的
2. 为什么不能cal cnt 时,判断 cnt[i][j]!=i && cnt[i][j]!=j 则累加答案? 因为有可能某最短路经过一系列比起点小的点更新了答案 起点是当中最大的 还真就是正确答案 我们排除的是起点到终点就一条边的情况 也只能在松弛操作里去判断 处理cnt
3. 为什么else里面不用判断起点? 因为起点是第一个入队的,不可能进入==的else的情况
4.当cnt[s][j]被更新 就要入队 即便最短路长相等
参考博客: