图论模板总结

单源最短路问题

单源最短路有三种算法,分别是朴素的dijistra算法,堆优化的dijidtra算法,和spfa算法
前面两种算法不能解决负权边
spfa和floyd可以求最长路

朴素版dijistra算法  复杂度:n*n
***输入一个有向图,以及起点be
***输出起点到每个点的距离dist[n]
***N和M代表数据范围,点数和边数
const int N=;M=;
int ne[M],to[M],h[N],w[M];
int cnt;
int vis[N];
int dist[N];
void add(int a,int b,int c){
    w[cnt]=c;to[cnt]=b;ne[cnt]=h[a];h[a]=cnt++;
}
void dijistra(int be){
	memset(dist,0x3f,sizeof(dist));
    memset(vis,0,sizeof(vis));
    dist[be]=1;
    vis[be]=1;
    int start=be;
    for( int i=1;i<n;i++){
        for( int j=h[start];j!=-1;j=ne[j]){
            int t=to[j];
            dist[t]=min(dist[start]+w[j],dist[t]);
        }
        int minn=0x7fffffff;
        for( int j=1;j<=n;j++){
            if(dist[j]<minn&&vis[j]==0){
                minn=dist[j];
                start=j;
            }    
        }
        vis[start]=1;
    }
}
堆优化版dijistra 复杂度 nlogm
int dist[N];
queue<int>q;
int vis[N];
struct node{
    int pos,val;
    bool operator <(const node &a ) const{
		return a.val<val;
	}
};
priority_queue<node>qu;
void dijistra(int be){
	while(!qu.empty()) qu.pop();
    memset(dist,0x3f,sizeof(dist));
    memset(vis,0,sizeof(vis));
    qu.push((node){be,0});
    dist[be]=0;
    while(!qu.empty()){
        node now=qu.top();
        qu.pop();
        if(vis[now.pos])continue;
        vis[now.pos]=1;
        for( int i=h[now.pos];i!=-1;i=ne[i]){
            int j=to[i];
            if(dist[j]>dist[now.pos]+w[i]){
                dist[j]=dist[now.pos]+w[i];
                qu.push ( (node){j,dist[j]} );
            }
        }
    }
}
spfa 复杂度 m不稳定最坏nm
queue<int>q;
void add( int a,int b,int c){
    w[cnt]=c;to[cnt]=b;ne[cnt]=h[a];h[a]=cnt++;
}
void spfa(int be){
    q.push(be);
    while(!q.empty()){
        int now=q.front();
        vis[now]=0;
        q.pop();
        for( int i=h[now];i!=-1;i=ne[i]){
            int j=to[i];
            if(dist[j]>dist[now]+w[i]){
                dist[j]=dist[now]+w[i];
                if(vis[j]==0){
                    vis[j]=1;
                    q.push(j);
                }
            }
        }
    }
}

多元最短路问题(待补充)

floyd算法用于求解多元最短路
算法复杂度为nnn 数据范围是200

***输入一个dist矩阵,代表每两个点之间的距离,没有路的两点距离是正无穷
***输出一个dist矩阵,代表两个点之间的距离
	memset(dist,0x3f,sizeof(dist));
	for()输入距离
	for( int i=1;i<=n;i++){//循环枚举所有顶点 
		for( int j=1;j<=n;j++){
			for( int k=1;k<=n;k++)
			dist[j][k]=min(dist[j][k],dist[j][i]+dist[i][k]);
		}
	}

最小生成树

kruskal加边法,基于并查集

算法复杂度 mlogm
		sort(a+1,a+1+m,cmp);//将所有边排序 
		long long ans=0;
		for(int i=1;i<=m;i++){
			if(findd(a[i].begin)!=findd(a[i].end)){//如果两个根不同,
			就是不同的集合 
				ans+=a[i].dis;
				merge(a[i].begin,a[i].end);//将两个集合合并 
			}
		}

prim加边法

算法复杂度 nn
ll prim(){//模板res返回最小生成树边权和
    int start=1;
    vis[1]=1;
    dist[1]=0;这里的dist代表最短的边,和dijidtera的dist含义不同
    ll res=0;
    for( int i=1;i<n;i++){
        for(int j=h[start];j!=-1;j=ne[j]){
            int t=to[j];
            dist[t]=min(w[j],dist[t]);//更新所有点到点集的最小值
        }
        int minn=0x7fffffff;
        for( int j=1;j<=n;j++){//找出离点集最近的点作为start
            if(maxx>dist[j]&&vis[j]==0){
                start=j;
                minn=dist[j];
            }
        }
        res+=dist[start];
    }
    return res;
}

spfa求负环

统计到某个点的最短路所经过点的个数,如果经过n个点,则说明存在负环。
(这里写第一种)
统计每个点的入队次数,如果某个点入队大于等于n次,则说明有负环
如果超时,可以统计所有点的入队次数,如果大于2n或3n一般存在负环

int spfa(){
    while(!q.empty()) q.pop();
    memset(dist,0,sizeof(dist));//简化
    memset(rem,0,sizeof(rem));
    for( int i=1;i<=n;i++){//简化
        q.push(i);
        vis[i]=1;
    }
    while(!q.empty()){
        int now=q.front();
        q.pop();
        vis[now]=0;
        for( int i=h[now];i!=-1;i=ne[i]){
            int j=to[i];
            if(dist[j]>dist[now]+w[i]){
                dist[j]=dist[now]+w[i];
                rem[j]=rem[now]+1;//注意步数统计要加在这里
                if(rem[j]>=n) return 1;
                if(!vis[j]){
                    vis[j]=1;
                    q.push(j);
                }
            }
        }
    }
    return 0;
}

有向图强连通分量

kosaraju算法,先对正图dfs再对反图dfs

int h[N][2],to[M][2],ne[M][2];
int cnt1,cnt0;
int add(int a,int b,int c){//1代表正向建图,0代表反向建图 
	if(c==1){
		to[cnt1][c]=b;ne[cnt1][c]=h[a][c];h[a][c]=cnt1++;
	}
	else {
		to[cnt0][c]=b;ne[cnt0][c]=h[a][c];h[a][c]=cnt0++;
	}
}
int vis[N];
stack<int>rem;
void dfs1(int u){
	vis[u]=1;
	for( int i=h[u][1];i!=-1;i=ne[i][1]){
		int j=to[i][1];
		if(vis[j]==0) dfs1(j);
	}
	rem.push(u);
}
void dfs2(int u){
	vis[u]=0;
	for( int i=h[u][0];i!=-1;i=ne[i][0]){
		int j=to[i][0];
		if(vis[j]==1) {
			f[j]=f[u];
			dfs2(j);
			这里可以维护强连通分量内点的某些性质例如最大值
		}
	}
	return;
}
下面是重新建图的函数,注意初始化head
注意初始化并查集
for( int i=1;i<=m;i++){
	if(f[u[i]!=f[v[i]]])add(f[u[i]] ,f[v[i]],1); 
}

无向图的双连通分量

差分约束

差分约束使用spfa求解
差分约束是求解不等式方程组
如果存在负环就无解
如果求两个变量最大值,将不等号变为<=,建图求最短路
如果求两个变量最小值,将不等号变为>=,建图求最长路

最近公共祖先(LCA)

树链剖分算法:
树链剖分用于维护树上任意两点到其公共祖先的某些性质

int d[N],f[N],size[N],son[N],top[N];
int dfs1( int fa,int rt){
	d[rt]=d[fa]+1;f[rt]=fa;
	size[rt]=1;son[rt]=0;//这两句不要忘 
	for( int i=h[rt];i!=-1;i=ne[i]){
		int j=to[i];
		if(j==fa) continue;
		else dfs1(rt,j);
		size[rt]+=size[j];
		if(size[son[rt]]<size[j])
			son[rt]=j;
		
	}
}
void dfs2( int rt,int fa){//先序遍历dfs 
	if(son[fa]==rt) top[rt]=top[fa];
	else top[rt]=rt;
	for( int i=h[rt];i!=-1;i=ne[i]){
		int j=to[i];
		if(j==fa) continue;
		else dfs2(j,rt);
	}
} 
int lca(int x,int y){
	while(top[x]!=top[y]){
		if(d[top[x]]<d[top[y]]) y=f[top[y]];
		else x=f[top[x]];
		//f[top]较大的点向上寻找 
	}
	return d[x]<d[y]?x:y;//返回深度较小的点 
}

倍增算法:

默认根节点是1,如果不是1,需要改正
int h[N], ne[M], to[M];
int cnt, root;
int fa[N][17];
void add(int a, int b)
{
    to[cnt] = b,ne[cnt] = h[a],h[a] = cnt++;
}
queue<int> q;
int depth[N];
void bfs()
{
    memset(depth,-1,sizeof(depth));
    depth[root] = 1;
    int now = root;
    q.push(now);
    while (!q.empty())
    {
        now = q.front();
        q.pop();
        for (int i = h[now]; i != -1; i = ne[i])
        {
            int j = to[i];
            if(depth[j]!=-1) continue;
            depth[j] = depth[now] + 1;
            q.push(j);
            fa[j][0] = now;
            for (int x = 1; x <= 16; x++)
            {
                fa[j][x] = fa[fa[j][x - 1]][x - 1];
            }
        }
    }
}
int lca(int x, int y)
{
    if (depth[x] < depth[y])
        swap(x, y);
    for (int i = 16; i >= 0; i--)
    {
        if (depth[fa[x][i]] >= depth[y])
        {
            x=fa[x][i];
        }
        
    }
    if(x==y) return y;
    for( int i=16;i>=0;i--){
        if(fa[x][i]!=fa[y][i]){
            x=fa[x][i],y=fa[y][i];
        }
    }
    return fa[x][0];
}

二分图

二分图的最小顶点覆盖=二分图的最大匹配
二分图的最小边、路径覆盖=v-二分图最大匹配
最大独立点集(任意两点在图中无对应边)=v-二分图最大匹配

时间复杂度为 ve
int l[N],r[N],vis[N];
int match[N];//注意下标是右边的数,储存左边 
int dfs(int u){
	for( int i=h[u];i!=-1;i++){
		int j=to[i];
		if(!vis[j]){
			vis[j]=1;
			if(match[j]==0||dfs(match[j])){
				match[j]=u;
				return true ;
			}
		}
	}
	return  false;
}
int hungary(){
	int res=0;//返回最大匹配数
	memset(h,-1,sizeof(h));
	memset(match,0,sizeof(match)); 
	for( int i=1;i<=n;i++){
		memset(vis,0,sizeof(vis));
		if(dfs(i)) res++;
	}
	return res;
}

欧拉回路

欧拉回路指存在一条路可以经过所有边并回到原点
判断欧拉回路和欧拉路的方法的方法:
1.首先使用dfs或者并查集判断图的连通性
2.无向图:如果图中的点全部是偶点,则存在欧拉回路,任意一点都可以作为起点和终点。如果只有两个奇点,则存在欧拉回路,其中一个奇点是起点,另一个值终点。
3.有向图:把一个点上的出度记为1,入度记为-1,这个点上所有入度和出度相加就是这个点的度数。一个有向图存在欧拉回路,当且仅当该图所有的点度数为0。如果只有一个度数为1的点,一个度数为-1的点,其他所有点的度数为0,那么存在欧拉路径,其中度数为1的点为起点,度数为0的点为终点。

拓扑排序

拓扑排序主要解决已知每两个元素之间的关系,求所有元素的排序

int n;
queue<int> ans;
void top_sort(){
	for( int i=1;i<=n;i++){
	/*	
		int count=0;
		for( int i=1;i<=n;i++){
			if(indeg[i]==0) count++;
		}
		if(count == 0) return ; 说明图中存在环路
		if(count > 1 ) return ;说明图中有两个点权重相同 
	*/
		for( int j=1;j<=n;j++){
			if(indeg[j]==0) {
				ans.push(j);
				break;
			}
		}
	}
}

2-SAT问题

分层图

分层图主要解决对原图进行至多k次变化的问题,将图建为k+1层
注意空间开k+1倍

int main(){
	int n;
	cin>>n;
	for( int i=1;i<=m;i++){
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);add(b,a,c);//第0层
		for( int j=1;j<=k;j++){//建第1~k层 
			add(a+j*n,b+j*n, c );
			add(b+j*n,a+j*n, c );
			add(a+j*n-n,b+j*n,___c);
			add(b+j*n-n,a+j*n,___c);
			//下层的图有一条通向上层的路径,但是上层没有通向下层的路径 
		} 
	}
}

割点与割边

判断割边只需要将low[j]>=num[rt]改为low[j]>num[rt]
如果不等式成立,那么( rt , j ) 就是割边

iscut标记割点,ans表示割点数量
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define N 20010
#define M 200100
int h[N],to[M],ne[M],cnt;
int low[N],num[N];//分别代表连接的最小点和时间戳
void add( int a,int b){
	to[cnt]=b;ne[cnt]=h[a];h[a]=cnt++;
} 
int ans=0;
int iscut[N];
int dfs( int rt,int fa){
	low[rt]=num[rt]=cnt++;
	int child=0;//搜索树的孩子数,不是原图的孩子数 
	for( int i=h[rt];i!=-1;i=ne[i]){	
		int j=to[i];
		if(j==fa) continue;
		if(num[j]==0){//该点是子节点 
			child++;
			low[rt]=min(dfs(j,rt),low[rt]);
			if(low[j]>=num[rt]&&iscut[rt]==0&&fa!=0){ 
				iscut[rt]=1;
				ans++;
			}
		}
		else if(num[j]<num[rt]){//该点是某个祖先节点 
			low[rt]=min(low[rt],num[j]);
		}
	}
	if(child>=2&&fa==0){//特判根节点 
		iscut[rt]=1;
		ans++;
	}
	return low[rt];
}
int main(){
	int n,m;
	cin>>n>>m;
	memset(h,-1,sizeof(h));
	memset(low,0x3f,sizeof(low));
	for( int i=1;i<=m;i++){
		int a,b;
		cin>>a>>b;
		add(a,b);add(b,a);
	}
	cnt=1;
	for( int i=1;i<=n;i++){//防止不连通 
		if( num[i]==0)
		dfs(i,0);
	}
	cout<<ans<<endl;
	for( int i=1;i<=n;i++){
		if(iscut[i]!=0) {
			printf("%d ",i);
		}
	}
}

图论的常用技巧

  1. 创建虚拟原点
  2. 将点权移动到边权
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值