五一劳动节快乐加餐(图论补充)--------P5905 【模板】Johnson 全源最短路

飞机票

本来是笑着做这个题的。。没想到越做越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
*/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值