本来是笑着做这个题的。。没想到越做越emo。。。只是看着代码实现花了快两个小时
题意:
思路:
题意还是非常明显的,第一问判断有没有负环,第二问算出,其中如果不存在则disi,j=1e9
那么比较直观的就是这题要求的是全源最短路,那么接下来再看数据范围:
ok~n=3e3,m=6e3,目前已知可以求全源最短路的算法有:
- Floyd:时间复杂度O(n3),可以处理负权边,但不能处理负环,而且速度很慢。
- Bellman-Ford:以每个点为源点做一次Bellman-Ford,时间复杂度O(n^2m),可以处理负权边,可以处理负环,但好像比Floyd还慢?
- dijkstra:以每个点为源点做一次dijkstra,时间复杂度O(nmlogm),不能处理负权边,但比前面两个快多了。
分析一下第一种,ok直接1e9飞上天肯定不可行,第二种1e9继续飞上天,第三种复杂度看起来是可行的,大概1e7左右?但是题目存在负环又不可行,直接寄,所以到底啥可行?
接下来,就该 Johnson 登场啦!Johnson 其实就是用另一种方法标记边权啦。
首先来看看实现方法:我们新建一个虚拟结点(不妨设他的编号为0),由他向其他的所有结点都连一条边权为0的边,然后求0号节点为源点的单源最短路,存到一个h数组中。然后,让每条边的权值w,变成w+hu-hv,这里u和v分别为这条边的起点和终点。然后再以每个点为源点做 dijkstra 就OK了。
那么如何求H数组呢?还有这样为啥是对的呢?这里贴一下别人解释
#include<bits/stdc++.h>
using namespace std;
typedef pair<int ,int >PII;
#define inf 1e9
const int maxn = 1e5;//点数最大值
int n, m, cnt;//n个点,m条边
struct Edge
{
int to, w, next;//终点,边权,同起点的上一条边的编号
}edge[maxn];//边集
int dis[5005];//第一遍更新的到超级源点的距离
int dist[5005];//第一遍更新的到超级源点的距离
bool vis[5005];//标记状态
int head[maxn];//head[i],表示以i为起点的第一条边在边集数组的位置(编号)
int sum[5005]; //出现的次数
void init()//初始化
{
for (int i = 0; i <= 5005; i++) head[i] = -1;
cnt = 0;
}
void add_edge(int u, int v, int w)//加边,u起点,v终点,w边权
{
edge[cnt].to = v; //终点
edge[cnt].w = w; //权值
edge[cnt].next = head[u];//以u为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号
head[u] = cnt++;//更新以u为起点上一条边的编号
}
bool spfa(){
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));//初始化
dis[0]=0;
vis[0]=true;
queue<int>q;
q.push(0);
while(q.size()){
int p=q.front();
q.pop();
vis[p]=false;
for(int i=head[p];i!=-1;i=edge[i].next){
int v=edge[i].to;
if(dis[v]>dis[p]+edge[i].w){
dis[v]=dis[p]+edge[i].w;
if(!vis[v]) {
vis[v]=true;
q.push(v);
sum[v]++;
if(sum[v]>=n+1) {
// cout<<"bug: ";
// cout<<n<<" "<<sum[v]<<" "<<v<<endl;
return true;
}
}
}
}
}
return false;
}
void dijkstra(int k)
{
for(int i=0;i<=n;i++) dist[i]=inf;
memset(vis,0,sizeof(vis));//初始化
priority_queue<PII,vector<PII>,greater<PII>>mo;
dist[k]=0;
long long ans=0;
mo.push({0,k});
while(mo.size())
{
auto t=mo.top();
mo.pop();
int ver=t.second,distance=t.first;
if(vis[ver]) continue;
vis[ver]=1;
for(int i=head[ver];i!=-1;i=edge[i].next)
{
int j=edge[i].to;
if(dist[j]>edge[i].w+distance)
{
dist[j]=edge[i].w+distance;
mo.push({dist[j],j});
}
}
}
for (int j=1;j<=n;j++)
if (dist[j]==inf)
ans+=1ll*j*inf;//如题目描述的最大值1e9
else
ans+=1ll*j*(dist[j]+dis[j]-dis[k]);
cout<<ans<<endl;
}
int main()
{
cin >> n >> m;
int u, v, w;
init();//初始化
for (int i = 1; i <= m; i++)//输入m条边
{
cin >> u >> v >> w;
add_edge(u, v, w);
}
for(int i=1;i<=n;i++) add_edge(0,i,0);//建虚拟节点0并且往其他的点都连一条边权为0的边
// for(int i = 0; i <cnt; i++)//n个起点
// {
// cout << i << endl;
// for(int j = head[i]; j != -1; j = edge[j].next)//遍历以i为起点的边
// {
// cout << i << " " << edge[j].to << " " << edge[j].w << endl;
// }
// cout << endl;
// }
if(spfa())//求h的同时也判了负环
{
printf("-1");
return 0;
}
for(int i=1;i<=n;i++){//每一个起点
for(int j=head[i];j!=-1;j=edge[j].next){//每一条边
edge[j].w=edge[j].w+dis[i]-dis[edge[j].to];
}
}
for(int i=1;i<=n;i++){
dijkstra(i);
}
return 0;
}
/*
5 7
1 2 1
2 3 2
3 4 3
1 3 4
4 1 5
1 5 6
4 5 7
*/