图论模板(更新中)

一些基础的跳过了,记录一些实用性强的模板


Dijkstra: O(n2) 适用于 权值为非负 的图的单源最短路径,用斐波那契堆的复杂度O(E+VlgV),
BellmanFord:适用于权值有负值的图的单源最短路径,并且能够检测负圈,复杂度O(VE)
SPFA:适用于权值有负值,且没有负圈的图的单源最短路径,论文中的复杂度O(kE),k为每个节点进入Queue的次数,且k一般<=2,但此处的复杂度证明是有问题的,其实SPFA的最坏情况应该是O(VE).
Floyd:每对节点之间的最短路径。Floyd-Warshall算法的时间复杂度为O(N3),空间复杂度为O(N2)。

先给出结论:
(1)当权值为非负时,用Dijkstra。
(2)当权值有负值,且没有负圈,则用SPFA,SPFA能检测负圈,但是不能输出负圈。
(3)当权值有负值,而且可能存在负圈,则用BellmanFord,能够检测并输出负圈。
(4)SPFA检测负环:当存在一个点入队大于等于V次,则有负环,后面有证明。

一张比较图

但以上比较都是没优化的。


堆优化的dijkstra算法 (洛谷p4479)

1、所有边权均是正数的单源最短路 O(mlogn)
2、适用于稠密图和顶点关系密切 即m和n一个数量级

#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e5+7;
bool vis[maxn];
typedef pair<int,int>pi;
int n,m,s,dis[maxn],head[maxn],cnt=0;
struct edge{	int to,w,next;	}e[maxn*5];
void add(int u,int v,int w){
    e[++cnt].w=w;
    e[cnt].to=v;
    e[cnt].next=head[u]; 
    head[u]=cnt;
}
inline void dijkstra(){
    for(int i=1; i<=n; i++) dis[i]=inf;
    dis[s]=0;
    priority_queue<pi>q;
    q.push(pi(0,s));
    while(!q.empty()){
        int x=q.top().second;
        q.pop();
        if(vis[x]) continue;
        vis[x]=1;
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(!vis[y]&&dis[y]>dis[x]+e[i].w){
                dis[y]=dis[x]+e[i].w;
                q.push(pi(-dis[y],y));
            }
        }
    }
}
 
int main(){
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
    }
    dijkstra();
    for(int i=1;i<=n;i++)printf("%d ",dis[i]);
}

/*
4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4


0 2 4 3

*/

SPFA(是Bellman-ford的队列优化)
判断有无负环:如果某个点进入队列的次数超过N次则存在负环,但SPFA无法处理带负环的图
一个SPFA入门的讲解

找了个入门题做例子:POJ 3268 Silver Cow Party(有向图多点到star和star到多点问题)

#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e4+7;
bool vis[maxn];
int n,m,s,dis[maxn],head[maxn],cnt=0;
struct edge{	int to,w,next;	}e[maxn*5];
void add(int u,int v,int w){
    e[++cnt].w=w;
    e[cnt].to=v;
    e[cnt].next=head[u]; 
    head[u]=cnt;
}
void spfa(){
	memset(dis,inf,sizeof(dis));
	memset(vis,0,sizeof(vis));
	dis[s]=0;	vis[s]=1;
	queue<int>q;	q.push(s);
	while(!q.empty()){
		int x=q.front();	q.pop();
		vis[x]=0;
		for(int i=head[x];i;i=e[i].next){
			int y=e[i].to,w=e[i].w;
			if(dis[y]>dis[x]+w){
				dis[y]=dis[x]+w;
				if(!vis[y]){
					q.push(y);
					vis[y]=1;
//如果要判断存在负边,加一个In[maxn]数组,存在返回flase 
//					if(++In[y]>n)	return 0;
				}
			}
		}
	}
}

int a[maxn],u[maxn],v[maxn],w[maxn],sum;
int main(){
	while(~scanf("%d%d%d",&n,&m,&s)){
		memset(head,0,sizeof(head));	memset(a,0,sizeof(a));	cnt=0;
		for(int i=0;i<m;i++){
			scanf("%d%d%d",&u[i],&v[i],&w[i]);
			add(u[i],v[i],w[i]);
		}
		spfa();
		for(int i=1;i<=n;i++)	a[i]+=dis[i];
		memset(head,0,sizeof(head));	cnt=0;
		for(int i=0;i<m;i++)	add(v[i],u[i],w[i]);
		spfa();
		for(int i=1;i<=n;i++)	a[i]+=dis[i];
		sum=0;
		for(int i=1;i<=n;i++)	sum=max(sum,a[i]);
		printf("%d\n",sum);
	}
}

Floyd O(n^3)
最不实用,如果数据100以下,可以无脑用。
例题:POJ - 3660 Cow Contest(里面还有闭包关系)

#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int maxn=100+7;
int a[maxn][maxn],n,m,x,y,sum;
void floyd(){
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++){
			if(!a[i][k])	continue;
			for(int j=1;j<=n;j++)
				a[i][j]|=a[i][k]&a[k][j];
		}
}
int main(){
	while(~scanf("%d%d",&n,&m)){
		memset(a,0,sizeof(a));	sum=0;
		for(int i=1;i<=m;i++){
			scanf("%d%d",&x,&y);
			a[x][y]=1;
		}
		floyd();
		for(int i=1;i<=n;i++){
			int t=0;
			for(int j=1;j<=n;j++){
				if(i==j)	continue;
				if(a[i][j]||a[j][i])	t++;
			}
			if(t==n-1)	sum++;
		}
		printf("%d\n",sum);
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值