Raki的图论入门到进阶算法简单总结

图论基础概念戳这里oiwiki

1.图的存储与遍历

图是相对线性数据结构更高级的模型,可以1对多也可以多对1,点与点之间,边与边之间都可以加上更多的限制,使其变得非常灵活,下面介绍两种最常用的图的存储方法

1.1 vector存图

用vector存图,会比较直观好懂

先弄出一个结构体, v v v为到达的顶点, w w w为这条边的边权

struct edge{
	int v,w;
};
vector<edge>e[maxn];

存储

e[u].push_back((edge){v,w});

遍历

for(auto x:e[u]){
	int v=x.v;
	int w=x.w;
}

1.2链式前向星式存图

int head[maxn],cnt;
struct edge{
	int v,w,nex;
}e[maxn];
inline void add(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
}

遍历

for(int i=head[u];i;i=e[i].nex){
		int v=e[i].v;
		int w=e[i].w;
	}

1.3 DFS

int vis[maxn];
void dfs(int u){
	if(vis[u])return;
	vis[u]=1;
	for(int i=head[u];i;i=e[i].nex){
		int v=e[i].v;
		if(!vis[v])dfs(v);
	}
	return;
}

1.4 BFS

int vis[maxn];
void bfs(int s){
	queue<int>q;
	q.push(s);
	vis[s]=1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!vis[v]){
				q.push(v);
				vis[v]=1;
			}
		}
	} 
}

2.最短路问题

最短路是图论中最简单也是比较常用的算法之一

2.1 Floyd算法

Floyd是基于dp思想进行求最短路,代码仅仅四行,非常好记,可以处理负权
算法复杂度 O ( N 3 ) O(N^3) O(N3)

void floyd(int n){
	memset(dis,0x3f,sizeof dis);
	for(int k=1;k<=n;k++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
			}
		}
	}
}

2.2 SPFA算法

spfa算法是基于bellman-ford算法的队列优化,可以处理负权,在随机图的情况下跑的速度非常快,接近于线性,最坏复杂度 O ( N ∗ M ) O(N*M) O(NM),正权图请不要用spfa求最短路,会被卡到最坏复杂度

void spfa(int s){
	queue<int>q;
	memset(dis,0x3f,sizeof dis); 
	memset(inque,0,sizeof inque);
	q.push(s);
	inque[s]=1;
	dis[s]=0;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		inque[u]=0;
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			int w=e[i].w;
			if(dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				if(!inque[v]){
					q.push(v);
					inque[v]=1;
				}
			}
		}
	}
}

2.3 Dijkstra算法

堆优化dijkstra算法的复杂度非常优秀,为 O ( M ∗ l o g N ) O(M*logN) O(MlogN),缺点是不能处理负权
需要对优先队列的<符号进行重载

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=2e5+5;
constexpr int inf=0x3f3f3f3f;
typedef pair<int,int>pii;
int cnt,n,m,s,vis[maxn],dis[maxn];
struct edge{
	int v,w;
};
vector<edge>e[maxn];
priority_queue<pii,vector<pii>,greater<pii>>q;
inline void dij(int s){
	memset(dis,inf,sizeof dis);
	memset(vis,0,sizeof vis);
	dis[s]=0;
	q.push({0,s});
	while(!q.empty()){
		pii x=q.top();
		q.pop();
		int u=x.second;
		if(vis[u])continue;
		vis[u]=1;
		for(auto ss:e[u]){
			int v=ss.v;
			if(dis[v]>dis[u]+ss.w){
				dis[v]=dis[u]+ss.w;
				q.push({dis[v],v});
			}
		}
	}
}
signed 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);
		e[u].push_back((edge){v,w});
	}
	dij(s);
	for(int i=1;i<=n;i++){
		printf("%d ",dis[i]);
	}
	return 0;
}

2.4 Johnson全源最短路算法

P5905 【模板】Johnson 全源最短路

比Floyd更快的全源最短路算法

前置需要掌握:堆优化dij算法+spfa判环

用势能转化,使得dij可以跑负权图,复杂度 O ( N ∗ M ∗ l o g N ) O(N*M*logN) O(NMlogN)

为什么这样是对的,详情请看oiwiki

#include<bits/stdc++.h>
using namespace std;
#define inf 1e9
const int maxn=5005,maxm=10005;
struct edge{
	int v,w,nex;
}e[maxm];
struct node{
	int dis,id;
	bool operator<(const node&a)const
	{
		return dis>a.dis;
	}
	node(int d,int x){
		dis=d,id=x;
	}
};
int head[maxn],vis[maxn],cs[maxn];
int cnt,n,m;
long long h[maxn],dis[maxn];
inline void add(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
}
bool spfa(int s){
	queue<int>q;
	memset(h,63,sizeof(h));
	h[s]=0,vis[s]=1;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(h[v]>h[u]+e[i].w){
				h[v]=h[u]+e[i].w;
				if(!vis[v]){
					vis[v]=1;
					q.push(v);
					cs[v]++;
					if(cs[v]==n)return false;
				}
			}
		}
	}
	return true;
}
inline void dij(int s){
	priority_queue<node>q;
	for(int i=1;i<=n;i++)
		dis[i]=inf;
	memset(vis,0,sizeof(vis));
	dis[s]=0;
	q.push(node(0,s));
	while(!q.empty()){
		int u=q.top().id;
		q.pop();
		if(vis[u])continue;
		vis[u]=1;
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(dis[v]>dis[u]+e[i].w){
				dis[v]=dis[u]+e[i].w;
				if(!vis[v])q.push(node(dis[v],v));
			}
		}
	}
	return;
}
int main(){
	ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        add(u,v,w);
    }
    for(int i=1;i<=n;i++){
        add(0,i,0);
    }
	if(!spfa(0)){
		cout<<-1<<endl;
		return 0;
	}
	for(int u=1;u<=n;u++)
		for(int i=head[u];i;i=e[i].nex)
			e[i].w+=h[u]-h[e[i].v];
	for(int i=1;i<=n;i++){
		dij(i);
		long long ans=0;
		for(int j=1;j<=n;j++){
			if(dis[j]==inf)ans+=j*inf;
			else ans+=j*(dis[j]+h[j]-h[i]);
		}
		cout<<ans<<endl;
	}
	return 0;
}

3.树上问题

树是一种特殊的图,有很多很好的性质

3.1 树的直径

树的直径的定义为树上距离最远的两点的距离

树的直径有两种求法,两次dfs和树形dp

各有优劣,dfs可以保存路径,树形dp可以处理负权

3.1.1 两次dfs求树的直径

int dis[500];
void dfs(int u,int fa){
	if(dis[u]>dis[pos]||pos==st)pos=u;
	f[u]=fa;
	for(int i=head[u];i;i=e[i].nex){
		int v=e[i].v;
		if(v==fa)continue;
		dis[v]=dis[u]+e[i].w;
		dfs(v,u);
	}
	return;
}
inline int  zj(){
	int st,pos;
	st=1.pos=1;
	dis[1]=0;
	dfs(st,0);
	st=pos;
	dis[st]=0;
	dfs(st,0);
	ed=pos;
	return dis[ed];
}

3.1.2 树形dp

#include <iostream>
#include <vector>
using namespace std;

const int N = int(1e4) + 9;
vector<int> adj[N];
int n, d;
int dfs(int u = 1, int p = -1) {
  int d1 = 0, d2 = 0;
  for (auto v : adj[u]) {
    if (v == p) continue;
    int d = dfs(v, u) + 1;
    if (d > d1)
      d2 = d1, d1 = d;
    else if (d > d2)
      d2 = d;
  }
  d = max(d, d1 + d2);
  return d1;
}
int main() {
  cin >> n;
  for (int i = 0; i < n - 1; ++i) {
    int a, b;
    cin >> a >> b;
    adj[a].push_back(b);
    adj[b].push_back(a);
  }
  dfs();
  cout << d << endl;
}

3.2 树的重心

对于树上的每一个点,计算其所有子树中最大的子树节点数,这个值最小的点就是这棵树的重心

void dfs(int u,int f){
	d[u]=1;
	int res=0;
	for(auto v:e[u]){
		if(v==f)continue;
		dfs(v,u);
		d[u]+=d[v];
		res=max(res,d[v]);
	}
	res=max(res,n-d[u]);
	if(res<maxn||(res==maxn&&ans>u)){
		maxn=res;
		ans=u;
	}
}

3.3 最近公共祖先(LCA)

最近公共祖先简称 LCA(Lowest Common Ancestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。

l c a lca lca有倍增法 和 t a r j a n tarjan tarjan算法离线求解

还有树剖可以快速求解,常数较小

这里介绍倍增法

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=5e5+5,maxm=1e6+5;
int head[maxm],cnt;
int dep[maxn],f[maxn][22],lg[maxn];
struct edge{
	int v,nex;
}e[maxm];

void add(int u,int v){
	e[++cnt].v=v;
	e[cnt].nex=head[u];
	head[u]=cnt;
}
inline void pre(int n){//预先算出log_2(i)+1的值,用的时候直接调用就可以了
	for(int i=1;i<=n;i++){
		lg[i]=lg[i-1]+(1<<lg[i-1]==i);
	}
}
void dfs(int u,int fa){ //u表示当前节点, fa表示父亲节点 
	f[u][0]=fa;
	dep[u]=dep[fa]+1;
	for(int i=1;i<=lg[dep[u]];i++){
		f[u][i]=f[f[u][i-1]][i-1];//意思是u的2^i祖先等于u的2^(i-1)祖先的2^(i-1)祖先
	}                             //2^i = 2^(i-1) + 2^(i-1)
	for(int i=head[u];i;i=e[i].nex){
		if(e[i].v==fa)continue;
		dfs(e[i].v,u);
	}
}
inline int lca(int u,int v){
	if(dep[u]<dep[v])swap(u,v);//用数学语言来说就是:不妨设x的深度 >= y的深度
	while(dep[u]>dep[v]){
		u=f[u][lg[dep[u]-dep[v]]-1];//先跳到同一深度
	}
	if(u==v)return u;//如果x是y的祖先,那他们的LCA肯定就是x了
	for(int i=lg[dep[u]]-1;i>=0;i--){//不断向上跳(lg就是之前说的常数优化)
		if(f[u][i]!=f[v][i]){//因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过去。
			u=f[u][i];
			v=f[v][i];
		}
	}
	return f[u][0];//返回父节点
}
int main(){
	int n,m,s;
	cin>>n>>m>>s;
	pre(n);
	for(int i=1;i<=n-1;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u);
	}
	dfs(s,0);
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		printf("%d\n",lca(u,v));
	}
	return 0;
}

3.4 点分治

点分治适合处理大规模的树上路径信息问题。

3.5 树链剖分

树链剖分用于将树分割成若干条链的形式,以维护树上路径的信息。
具体来说,将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息。

重链剖分维护两点路径上的权值和某点子树的权值:

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=1e5+5,inf=0x3f3f3f3f;
vector<int>e[maxn];
int dep[maxn],f[maxn],sz[maxn],son[maxn];
int id[maxn],wt[maxn],w[maxn],top[maxn],cnt;
int b[maxn*3],d[maxn*3];
int n,m,r,mod;
inline void update(int p){
	d[p]=d[p*2]+d[p*2+1];
	d[p]%=mod;
}
void build(int l,int r,int p){
	if(l==r){
		d[p]=wt[l];
		return;
	}
	int mid=(l+r)>>1;
	build(l,mid,2*p),build(mid+1,r,2*p+1);
	update(p);
}
void pushdown(int l,int r,int s,int t,int mid,int p){
	d[p*2]+=b[p]*(mid-s+1);
	d[p*2+1]+=b[p]*(t-mid);
	b[p*2]+=b[p];
	b[p*2+1]+=b[p];
	b[p]=0;
}
void add(int l,int r,int c,int s,int t,int p){
	if(l<=s&&t<=r){
		d[p]+=(t-s+1)*c;
		b[p]+=c;
		return;
	}
	int mid=(s+t)/2;
	if(b[p]&&s!=t){
		pushdown(l,r,s,t,mid,p);
	}
	if(l<=mid)add(l,r,c,s,mid,p*2);
	if(r>mid) add(l,r,c,mid+1,t,p*2+1);
	update(p);
}
int getsum(int l,int r,int s,int t,int p){
	if(l<=s&&t<=r)return d[p];
	int mid=(s+t)/2;
	if(b[p]){
		pushdown(l,r,s,t,mid,p);
	}
	int sum=0;
	if(l<=mid)sum+=getsum(l,r,s,mid,p*2);
	if(r>mid) sum+=getsum(l,r,mid+1,t,p*2+1);
	return sum;
}
void dfs1(int u,int fa,int depth){
	dep[u]=depth;
	f[u]=fa;
	sz[u]=1;
	int maxson=-1;
	for(auto v:e[u]){
		if(v==fa)continue;
		dfs1(v,u,depth+1);
		sz[u]+=sz[v];
		if(sz[v]>maxson){
			maxson=sz[v];
			son[u]=v;
		}
	}
}
void dfs2(int u,int topf){
	id[u]=++cnt;
	wt[cnt]=w[u];
	top[u]=topf;
	if(!son[u])return;
	dfs2(son[u],topf);
	for(auto v:e[u]){
		if(v==f[u]||v==son[u])continue;
		dfs2(v,v);
	}
}
int range_getsum(int x,int y){
	int ans=0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		ans+=getsum(id[top[x]],id[x],1,n,1);
		ans%=mod;
		x=f[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	ans+=getsum(id[x],id[y],1,n,1);
	return ans%mod;

}
void range_add(int x,int y,int c){
	c%=mod;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		add(id[top[x]],id[x],c,1,n,1);
		x=f[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	add(id[x],id[y],c,1,n,1);
}
inline int son_getsum(int x){
	return (getsum(id[x],id[x]+sz[x]-1,1,n,1))%mod;
}
inline void son_add(int x,int c){
	add(id[x],id[x]+sz[x]-1,c,1,n,1);
}
int lca(int u, int v) {
  while (top[u] != top[v]) {
    if (dep[top[u]] > dep[top[v]])u = f[top[u]];
    else v = f[top[v]];
  }
  return dep[u] > dep[v] ? v : u;
}
int main(){
	cin>>n>>m>>r>>mod;
	for(int i=1;i<=n;i++){
		cin>>w[i];
	}
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		e[u].emplace_back(v);
		e[v].emplace_back(u);
	}
	dfs1(r,r,1);
	dfs2(r,r);
	build(1,n,1);
	for(int i=1;i<=m;i++){
		int ch,x,y,z;
		cin>>ch;
		if(ch==1){
			cin>>x>>y>>z;
			range_add(x,y,z);
		}
		else if(ch==2){
			cin>>x>>y;
			printf("%lld\n",range_getsum(x,y));
		}
		else if(ch==3){
			cin>>x>>y;
			son_add(x,y);
		}
		else{
			cin>>x;
			printf("%lld\n",son_getsum(x));
		}
	}
	return 0;
}

4.生成树

我们定义无向连通图的 最小生成树 (Minimum Spanning Tree,MST)为边权和最小的生成树。

注意:只有连通图才有生成树,而对于非连通图,只存在生成森林

4.1.Kruskal算法

k r u s k a l kruskal kruskal是基于并查集的一种求解最小生成树的算法,复杂度非常优秀,求解的思想是优先选择最小边,若两端点不属于同一祖先,则加上

#include<bits/stdc++.h>
using namespace std;
int n,m,a[5005],ans,cnt;
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*f;
}
struct edge{
	int u,v,w;
}e[200005];
int find(int x){
	if(a[x]==x)return x;
	return a[x]=find(a[x]);
}
void hb(int y,int x){
	a[find(y)]=find(x);
	return;
}
bool cmp(edge a,edge b){
	return a.w<b.w;
}
void krus(){
	sort(e+1,e+1+m,cmp);
	for(int i=1;i<=m;i++){
		int u,v;
		u=e[i].u;
		v=e[i].v;
		if(find(u)==find(v))continue;
		ans+=e[i].w;
		hb(u,v);
		if(++cnt==n-1)break;
	}
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		a[i]=i;
	}
	for(int i=1;i<=m;i++){
		e[i].u=read();
		e[i].v=read();
		e[i].w=read();
	} 
	krus();
	cout<<ans;
	return 0;
}

4.2堆优化Prim算法

p r i m prim prim算法的思想跟 d i j dij dij一样,是选取一个点,并贪心的选择与这个点边权最小的边,并也可用优先队列来优化,在稠密图下复杂度表现良好,非稠密图不建议使用

#include<bits/stdc++.h>
using namespace std;
int  k,n,m,cnt,ans,sum;
int dis[5005],vis[5005];
struct edge{
	int v,w;
};
struct node{
	int w,now;
	bool operator <(const node&x)const
	{
		return w>x.w;
	}
};
vector<edge>g[5005];
priority_queue<node>q;
void prim(){
	memset(dis,0x3f,sizeof(dis));
	dis[1]=0;
	q.push((node){0,1});
	while(!q.empty()&&cnt<n){
		node x=q.top();
		q.pop();
		int d=x.w;
		int u=x.now;
		if(vis[u])continue;
		cnt++;
		sum+=d;
		vis[u]=1;
		for(int i=0;i<g[u].size();i++){
			if(g[u][i].w<dis[g[u][i].v]){
				dis[g[u][i].v]=g[u][i].w;
				q.push((node){dis[g[u][i].v],g[u][i].v});
			}
		}
	}
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		g[u].push_back((edge){v,w});
		g[v].push_back((edge){u,w});
	}
	prim();
	if(cnt==n)cout<<sum;
	else cout<<"orz";
	return 0;
}

4.3最小树形图-朱刘算法

最小树形图就是有向图的最小生成树

kuangbin最小树形图模板

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
using namespace std;
const int inf =0x3f3f3f3f;
const int maxn=1005;
struct Edge{
    int u, v, w;
    Edge(int uu, int vv, int ww){u = uu, v = vv, w = ww;}
    Edge(){}
}es[maxn*maxn];
struct pos{
	double x,y;
}pos[105];
int pre[maxn], id[maxn], vis[maxn], in[maxn];
int zhuliu(int root, int n, int m){
    int res = 0, u, v;
    while(true){
        fill(in, in+n, inf);
        for(int i = 0; i < m; ++i)
            if(es[i].u != es[i].v && es[i].w < in[es[i].v]){
                pre[es[i].v] = es[i].u;
                in[es[i].v] = es[i].w;
            }
        for(int i = 0; i < n; ++i)
            if(i!=root && in[i]==inf)
                return -1;
        int tn = 0;
        memset(id,-1,sizeof(id));
        memset(vis,-1,sizeof(vis));
        in[root] = 0;
        for(int i = 0; i < n; ++i){
            res += in[i];
            v = i;
            while(vis[v]!=i && id[v]==-1 && v!=root){
                vis[v] = i;
                v = pre[v];
            }
            if(v!=root && id[v]==-1){
                for(int u = pre[v]; u != v; u = pre[u])
                    id[u] = tn;
                id[v] = tn++;
            }
        }
        if(tn == 0) break;

        for(int i = 0; i < n; ++i)
            if(id[i]==-1)
                id[i] = tn++;
        for(int i = 0; i < m;){
            v = es[i].v;
            es[i].u = id[es[i].u];
            es[i].v = id[es[i].v];
            if(es[i].u != es[i].v)
                es[i++].w -= in[v];
            else
                swap(es[i], es[--m]);
        }
        n = tn;
        root = id[root];
    }
    return res;
}
int n,m,w[1005][1005];
int main(){
	int tt,a,b,c;
	while(scanf("%d%d",&n,&m)!=EOF){
		memset(w,inf,sizeof(w));
		cin>>n>>m;
		for(int i=0;i<m;i++){
			scanf("%d%d%d",&pos[i].x,&pos[i].y);
		//	if(a==b)continue;
		//	w[a][b]=min(w[a][b],c);
		}
		int l=0;
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				
			}
		}
		int ans=zhuliu(0,n,l);
		printf("Case #%d: ",qq);
        if(ans==-1) printf("Possums!\n");
        else printf("%d\n",ans);
	}
	return 0;
}

5.拓扑排序

拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点。

void topo(){
	queue<int>q;
	for(int i=1;i<=n;i++){
		if(du[i]==0){
			q.push(i);
			vis[i]=1;
		}
	}
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			du[v]--;
			if(!vis[v]&&du[v]==0){
				vis[v]=1;
				q.push(v);
			}
		}
	}
} 

6.差分约束

在这里插入图片描述

7.图的连通性问题

强连通的定义是:有向图 G 强连通是指,G 中任意两个结点连通。
强连通分量(Strongly Connected Components,SCC)的定义是:极大的强连通子图。

7.1 强连通分量缩点

void tarjan(int u){
	dfn[u]=low[u]=++tot;
	z[++top]=u;//入栈 
	vis[u]=1;
	for(auto v:e[u]){
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v]){
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u]){
		t++;//连通分量的标号 
		do{
			color[z[top]]=t;    //属于这个连通分量 
			cnt[t]++;         //记录这个环中有多少个点 
			vis[z[top]]=0; 
			top--;              //出栈 
		}while(u!=z[top+1]);
	}
}

7.2 双连通分量

在一张连通的无向图中,对于两个点u和v,如果无论删去哪条边(只能删去一条)都不能使它们不连通,我们就说u和v边双连通 。
在一张连通的无向图中,对于两个点 u和v,如果无论删去哪个点(只能删去一个,且不能删u和v自己)都不能使它们不连通,我们就说u和v点双连通 。
边双连通具有传递性,即,若x,y边双连通, y,z 边双连通,则 x,z 边双连通。
点双连通 不 具有传递性,反例如下图,a,b点双连通, b,c点双连通,而a,c不点双连通。

在这里插入图片描述

7.3 割点

对于一个无向图,如果把一个点删除后这个图的极大连通分量数增加了,那么这个点就是这个图的割点(又称割顶)。

void tarjan(int u,int root){//root代表此树的根 
	int child=0;
	dfn[u]=low[u]=++tot;
	for(int i=0;i<e[u].size();i++){
		int v=e[u][i];
		if(!dfn[v]){
			tarjan(v,root);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u]&&u!=root)cut[u]=true;
			if(u==root)child++;
		}
		low[u]=min(low[u],dfn[v]);
	}
	if(u==root&&child>=2){
		cut[root]=true;
	}
}

7.4 桥

对于一个无向图,如果删掉一条边后图中的连通分量数增加了,则称这条边为桥或者割边。

void tarjan(int u,int root){
	dfn[u]=low[u]=++tot;
	for(int i=head[u];i;i=e[i].nex){
		int v=e[i].v;
		if(!dfn[v]){
			tarjan(v,i);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u]){
				qiao[i]=1;
			}
		}
		else if(i!=(root^1))low[u]=min(low[u],dfn[v]);
	}
	return;
}

8.二分图最大匹配问题

二分图,又称二部图,英文名叫 Bipartite graph。
二分图是什么?节点由两个集合组成,且两个集合内部没有边的图。
换言之,存在一种方案,将节点划分成满足以上性质的两个集合。
如图:
在这里插入图片描述

8.1 匈牙利算法

常数非常优秀,时间复杂度 O ( N ∗ M ) O(N*M) O(NM)

bool dfs(int u){
	for(int v=m+1;v<=n;v++){
		if(vis[v]||!e[u][v])continue;
		vis[v]=1;
		if(!match[v]||dfs(match[v])){// 如果v没有匹配,或者v的匹配找到了新的匹配
			match[v]=u;
			match[u]=v;          // 更新匹配信息
			return true;
		}
	}
	return false;
}
int xyl(){
	int ans=0;
	memset(match,0,sizeof(match));
	for(int i=1;i<=n;i++){// 对每一个点尝试匹配
		memset(vis,0,sizeof(vis));
		if(dfs(i))ans++;
	}
	return ans;
}

8.2 Dinic求二分图最大匹配

建完二分图直接跑最大流即可,常数比匈牙利算法大,复杂度稍优秀,时间复杂度 O ( s q r t ( n ) ∗ M ) O(sqrt(n)*M) O(sqrt(n)M)

8.3 二分图最大权完美匹配问题-KM算法

缺陷在于只能用于求解完美匹配的二分图最大权问题,时间复杂度为 O ( N 3 ) O(N^3) O(N3), 相较于 S P F A SPFA SPFA费用流的 O ( N ∗ M ∗ F ) O(N*M*F) O(NMF)高效很多

(原理我没去弄懂,当黑盒用)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const long long maxn=1005,inf=9e18;
int n,m,mp[maxn][maxn],matched[maxn];
int slack[maxn],ex[maxn],ey[maxn],pre[maxn];
int visx[maxn],visy[maxn];
void match(int u){
	int x,y=0,yy=0,d;
	memset(pre,0,sizeof(pre));
	for(int i=1;i<=n;i++)slack[i]=inf;
    matched[y]=u;
	while(1){
		x=matched[y];
		d=inf;
		visy[y]=1;
		for(int i=1;i<=n;i++){
			if(visy[i])continue;
			if(slack[i]>ex[x]+ey[i]-mp[x][i]){
				slack[i]=ex[x]+ey[i]-mp[x][i];
				pre[i]=y;
			}
			if(slack[i]<d){
				d=slack[i];
				yy=i;
			}
		}
		for(int i=0;i<=n;i++){
			if(visy[i])ex[matched[i]]-=d,ey[i]+=d;
			else slack[i]-=d;
		}
		y=yy;
		if(matched[y]==-1)break;
	}
	while(y){
		matched[y]=matched[pre[y]];
		y=pre[y];
	}
}
int km(){
	memset(matched,-1,sizeof(matched));
	memset(ex,0,sizeof(ex));
	memset(ey,0,sizeof(ey));
	for(int i=1;i<=n;i++){
		memset(visy,0,sizeof(visy));
		match(i);
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		if(matched[i]!=-1)ans+=mp[matched[i]][i];
	}
	return ans;
}
signed main()
{	
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            mp[i][j]=-inf;
    for(int i=1;i<=m;i++)
    {	
        int u,v,w;
        scanf("%lld%lld%lld",&u,&v,&w);
        mp[u][v]=w;
    }
    printf("%lld\n",km());
    for(int i=1;i<=n;i++){
    	printf("%lld ",matched[i]);
	}
    return 0;
}

9.一般图最大匹配问题

一般图匹配和二分图匹配(bipartite matching)不同的是,图可能存在奇环。

9.1 一般图最大匹配算法-带花树

用于求解一般图最大匹配问题

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+5,maxm=2e5+5;
int n,m,cnt=1,head[maxn];
int match[maxn],pre[maxn],f[maxn],vis[maxn],tim,dfn[maxn];
struct edge{
	int v,nex;
}e[maxm];
inline void add(int u,int v){
	e[++cnt].v=v;
	e[cnt].nex=head[u];
	head[u]=cnt;
}
int find(int x){
	return x==f[x]?x:f[x]=find(f[x]);
}
int lca(int u,int v){
	++tim;
	u=find(u);
	v=find(v);
	while(dfn[u]!=tim){
		dfn[u]=tim;
		u=find(pre[match[u]]);
		if(v)swap(u,v);
	}
	return u;
}
queue<int>q;
void blossom(int x,int y,int w){
	while(find(x)!=w){
		pre[x]=y;
		y=match[x];
		if(vis[y]==2){
			vis[y]=1;
			q.push(y);
		}
		if(find(x)==x)f[x]=w;
		if(find(y)==y)f[y]=w;
		x=pre[y];
	}
}
bool aug(int s){
	for(int i=1;i<=n;i++){
		f[i]=i;
		vis[i]=pre[i]=0;
	}
	while(!q.empty()){
		q.pop();
	}
	q.push(s);
	vis[s]=1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(find(u)==find(v)||vis[v]==2)continue;
			if(!vis[v]){
				vis[v]=2;
				pre[v]=u;
				if(!match[v]){
					for(int x=v,last;x;x=last){
						last=match[pre[x]];
						match[x]=pre[x];
						match[pre[x]]=x;
					}
					return true;
				}
				vis[match[v]]=1;
				q.push(match[v]);
			}
			else{
				int w=lca(u,v);
				blossom(u,v,w);
				blossom(v,u,w);
			}
		}
	}
	return false;
}
int main(){
	cin>>n>>m;
	int ans=0; 
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u);
	}
	for(int i=1;i<=n;i++){
		if(!match[i])ans+=aug(i);
	}
	printf("%d\n",ans);
	for(int i=1;i<=n;i++){
		printf("%d ",match[i]);
	}
	return 0;
}

9.2 一般图最大权匹配-带权带花树

原子弹级算法,几乎不太可能遇(遇到了也做不出)

贴上uoj的板子:一般图最大权匹配

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define dist(e) (lab[e.u]+lab[e.v]-g[e.u][e.v].w*2)
const int N = 1023, inf = 1e9;
struct edge{int u, v, w;}g[N][N];
int n, m, n_x, lab[N], match[N], slk[N], st[N];
int pa[N], ff[N][N], S[N], vis[N];
vector <int> flower[N];
deque <int> q;
void update(int u, int x) {//updateSlk
    if (!slk[x] || dist(g[u][x]) < dist(g[slk[x]][x]))
        slk[x] = u;
}
void setSlk(int x) {
    slk[x] = 0;
    for (int u = 1; u <= n; u ++)
        if (g[u][x].w > 0 && st[u] != x && S[st[u]] == 0)
            update(u, x);
}
void qpush(int x) {
    if (x <= n) return q.push_back(x);
    for (int i : flower[x]) qpush(i);
}
void setSt(int x, int b) {
    st[x] = b; if (x <= n) return;
    for (int i : flower[x]) setSt(i, b);
}
int getPr(int b, int xr) {
    int pr = find(flower[b].begin(), flower[b].end(), xr) - flower[b].begin();
    if (pr & 1) {
        reverse(flower[b].begin() + 1, flower[b].end());
        return (int)(flower[b].size()) - pr;
    }
    return pr;
}
void setMatch(int u, int v) {
    match[u] = g[u][v].v; if (u <= n) return;
    int xr = ff[u][g[u][v].u], pr= getPr(u, xr);
    for (int i = 0; i < pr; i ++)
        setMatch(flower[u][i], flower[u][i ^ 1]);
    setMatch(xr, v);
    rotate(flower[u].begin(), flower[u].begin() + pr, flower[u].end());
}
void augment(int u, int v) {
    int xnv = st[match[u]]; setMatch(u, v);
    if (!xnv) return; setMatch(xnv, st[pa[xnv]]);
    augment(st[pa[xnv]], xnv);
}
int getLca(int u, int v) {
    static int t = 0;
    for (t ++; u || v; swap(u, v)) {
        if (!u) continue;
        if (vis[u] == t) return u;
        vis[u] = t, u = st[match[u]];
        if (u) u = st[pa[u]];
    }
    return 0;
}
void addBlossom(int u, int lca, int v) {
    int b = n + 1;
    while (b <= n_x && st[b]) b ++;
    if (b > n_x) n_x ++;
    lab[b] = 0, S[b] = 0; match[b] = match[lca];
    flower[b].clear(); flower[b].push_back(lca);
    for (int x = u,y; x != lca; x = st[pa[y]])
        flower[b].push_back(x), flower[b].push_back(y = st[match[x]]), qpush(y);
    reverse(flower[b].begin() + 1, flower[b].end());
    for (int x = v, y; x != lca; x = st[pa[y]])
        flower[b].push_back(x), flower[b].push_back(y = st[match[x]]), qpush(y);
    setSt(b, b);
    for (int x = 1; x <= n_x; ++ x) g[b][x].w = g[x][b].w = 0;
    for (int x = 1; x <= n; ++ x)   ff[b][x] = 0;
    for (int i = 0; i < flower[b].size(); ++ i) {
        int xs = flower[b][i];
        for (int x = 1; x <= n_x; ++ x)
            if (g[b][x].w == 0 || dist(g[xs][x]) < dist(g[b][x]))
                g[b][x] = g[xs][x], g[x][b] = g[x][xs];
        for (int x = 1; x <= n; ++ x)
            if (ff[xs][x]) ff[b][x] = xs;
    }
    setSlk(b);
}
void expandBlossom(int b) {
    for (int i = 0; i < flower[b].size(); ++ i)
        setSt(flower[b][i], flower[b][i]);
    int xr = ff[b][g[b][pa[b]].u], pr = getPr(b, xr);
    for (int i = 0; i < pr; i += 2) {
        int xs = flower[b][i], xns = flower[b][i + 1];
        pa[xs] = g[xns][xs].u; S[xs] = 1, S[xns] = 0;
        slk[xs] = 0, setSlk(xns); qpush(xns);
    }
    S[xr] = 1, pa[xr] = pa[b];
    for (int i = pr + 1; i < flower[b].size(); ++ i) {
        int xs = flower[b][i];
        S[xs] = -1, setSlk(xs);
    }
    st[b] = 0;
}
bool onFoundedge(const edge &e) {
    int u = st[e.u], v = st[e.v];
    if (S[v] == -1) {
        pa[v] = e.u, S[v] = 1;
        int nu = st[match[v]];
        slk[v] = slk[nu] = 0;
        S[nu] = 0, qpush(nu);
    }
    else if (S[v] == 0) {
        int lca = getLca(u, v);
        if (!lca) return augment(u, v), augment(v, u), 1;
        else addBlossom(u, lca, v);
    }
    return 0;
}
bool matching() {
    fill(S, S + n_x + 1, -1), fill(slk, slk + n_x + 1, 0); q.clear();
    for (int x = 1; x <= n_x; ++ x)
        if (st[x] == x && !match[x])
            pa[x] = 0, S[x] = 0, qpush(x);
    if (q.empty()) return 0;
    while (1) {
        while (q.size()) {
            int u = q.front(); q.pop_front();
            if (S[st[u]] == 1) continue;
            for (int v = 1; v <= n; ++v)
                if (g[u][v].w > 0 && st[u] != st[v]) {
                    if (dist(g[u][v]) == 0) {
                        if (onFoundedge(g[u][v]))
                            return 1;
                    }
                    else update(u, st[v]);
                }
        }
        int d = inf;
        for (int b = n + 1; b <= n_x; ++ b)
            if (st[b] == b && S[b] == 1)
                d = min(d, lab[b] / 2);
        for (int x = 1; x <= n_x; ++ x)
            if (st[x] == x && slk[x]) {
                if (S[x] == -1)     d = min(d, dist(g[slk[x]][x]));
                else if (S[x] == 0) d = min(d, dist(g[slk[x]][x]) / 2);
            }
        for (int u = 1; u <= n; ++ u) {
            if (S[st[u]] == 0) {
                if (lab[u] <= d) return 0;
                lab[u] -= d;
            }
            else if (S[st[u]] == 1) lab[u] += d;
        }
        for (int b = n + 1; b <= n_x; ++ b)
            if (st[b] == b) {
                if (S[st[b]] == 0)      lab[b] += d * 2;
                else if (S[st[b]] == 1) lab[b] -= d * 2;
            }
        q.clear();
        for (int x = 1; x <= n_x; ++ x)
            if (st[x] == x && slk[x] && st[slk[x]] != x && dist(g[slk[x]][x]) == 0)
                if (onFoundedge(g[slk[x]][x])) return 1;
        for (int b = n + 1; b <= n_x; ++ b)
            if (st[b] == b && S[b] == 1 && lab[b] == 0)
                expandBlossom(b);
    }
    return 0;
}
pair<ll, int> weightBlossom() {
    fill (match, match + n + 1, 0);
    n_x = n; int n_matches = 0, w_max = 0;
    ll tot_weight = 0;
    for (int u = 0; u <= n; ++ u) st[u] = u, flower[u].clear();
    for (int u = 1; u <= n; ++ u)
        for (int v = 1; v <= n; ++ v) {
            ff[u][v] = (u == v ? u : 0);
            w_max = max(w_max, g[u][v].w);
        }
    for (int u = 1; u <= n; ++ u) lab[u] = w_max;
    while (matching()) n_matches ++;
    for (int u = 1; u <= n; ++ u)
        if (match[u] && match[u] < u)
            tot_weight += g[u][match[u]].w;
    return make_pair(tot_weight, n_matches);
}
int main() {
    cin >> n >> m;
    for(int u = 1; u <= n; ++ u)
        for(int v = 1; v <= n; ++ v)
            g[u][v] = edge{u, v, 0};
    for(int u, v, w; m --; ) {
        cin >> u >> v >> w;
        g[u][v].w = g[v][u].w = w;
    }
    cout << weightBlossom().first << '\n';
    for (int u = 1; u <= n; ++ u) cout << match[u] << ' ';
}   

10.网络流

最大流:
我们有一张图,要求从源点流向汇点的最大流量(可以有很多条路到达汇点),就是我们的最大流问题。

最小费用最大流:
最小费用最大流问题是这样的:每条边都有一个费用,代表单位流量流过这条边的开销。我们要在求出最大流的同时,要求花费的费用最小。

10.1 Dinic算法求网络最大流

多路增广+当前弧优化,跑的飞快, 时间复杂度 O ( N 2 ∗ M ) O(N^2*M) O(N2M)

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
int cnt=1,n,m,s,t;
struct edge{
	int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].w=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
bool bfs(){
	memset(dep,0,sizeof(dep));
    for(int i=1;i<=n;i++){//如果要拆点或者其他的一定记得把范围开大点!!! 
    	cur[i]=head[i];
	}
	dep[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!dep[v]&&e[i].w){
				dep[v]=dep[u]+1;
				q.push(v);
				if(v==t)return true;		
			}
		}
	}
	return false;
}
int dfs(int u,int now){
	if(u==t||now==0){
		return now;
	}
	int flow=0,rlow=0;
	for(int i=cur[u];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(now,e[i].w))){
				flow+=rlow;
				now-=rlow;
				e[i].w-=rlow;
				e[i^1].w+=rlow;
				if(now==0)return flow;
			}
		}
	}
	if(!flow)dep[u]=-1;
	return flow;
}
int dinic(){
	int maxflow=0;
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
int main(){
	scanf("%d%d%d%d",&n,&m,&s,&t);
	int u,v,w;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&u,&v,&w);
		add_edge(u,v,w);
	}
	printf("%d",dinic());
	return 0;
}

10.2 类Dinic算法-SPFA费用流

多路增广的费用流,时间复杂度 O ( N ∗ M ∗ F ) O(N*M*F) O(NMF)

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f,maxn=5e3+5,maxm=5e4+5;
int n,m,s,t,cost,maxflow,vis[maxn],dis[maxn];
struct edge{
	int v,flow,cost,rev;
};
vector<edge>e[maxm*2];
inline void add_edge(int u,int v,int flow,int cost){
	e[u].push_back((edge){v,flow,cost,e[v].size()});
	e[v].push_back((edge){u,0,-cost,e[u].size()-1});
}
bool spfa(){
	memset(vis,0,sizeof(vis));
	memset(dis,inf,sizeof(dis));
	dis[s]=0;
	vis[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=0;i<e[u].size();i++){
			int v=e[u][i].v;
			int c=e[u][i].cost;
			if(dis[v]>dis[u]+c&&e[u][i].flow){
				dis[v]=dis[u]+c;
				if(vis[v]==0){
					q.push(v);
					vis[v]=1;
				}
			}
		}
	}
	if(dis[t]!=inf)return true;
	return false;
}
int dfs(int u,int flow){
	if(u==t){
		vis[t]=1;
		maxflow+=flow;
		return flow;
	}
	int used=0;
	vis[u]=1;
	for(int i=0;i<e[u].size();i++){
		int v=e[u][i].v;
		int c=e[u][i].cost;
		if((vis[v]==0||v==t)&&e[u][i].flow!=0&&dis[v]==dis[u]+c){
			int minflow=dfs(v,min(flow-used,e[u][i].flow));
			if(minflow!=0){
				cost+=c*minflow;
				e[u][i].flow-=minflow;
				e[v][e[u][i].rev].flow+=minflow;
				used+=minflow;
				if(used==flow)break;
			}
		}
	}
	return used;
}
int mcmf(){
	while(spfa()){
		vis[t]=1;
		while(vis[t]){
			memset(vis,0,sizeof(vis));
			dfs(s,inf);
		}
	}
	return maxflow;
}
int main(){
	scanf("%d%d%d%d",&n,&m,&s,&t);
	int u,v,f,w;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d%d",&u,&v,&f,&w);
	    add_edge(u,v,f,w);
	}
	mcmf();
	printf("%d %d",maxflow,cost);
	return 0;
}

10.3 EK费用流

效率没有上一种费用流高,但是优点在于可以记录每次增广的费用和流量,方便动态操作

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=0x3f3f3f3f,maxn=1005,maxm=100005;
struct edge{
	int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m;
inline void add_edge(int u,int v,int cost,int flow){
	e[++cnt].v=v;
	e[cnt].cost=cost;
	e[cnt].flow=flow;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].cost=-cost;
	e[cnt].flow=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
int inque[maxn];
int dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){
	queue<int>q;
	memset(dis,0x3f,sizeof dis);
	memset(flow,0x3f,sizeof flow);
	memset(inque,0,sizeof inque);
	q.push(s);
	inque[s]=1;
	dis[s]=0;
	pre[t]=-1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		inque[u]=0;
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(dis[v]>dis[u]+e[i].cost&&e[i].flow){
				dis[v]=dis[u]+e[i].cost;
				pre[v]=u;
				last[v]=i;
				flow[v]=min(flow[u],e[i].flow);
				if(!inque[v]){
					q.push(v);
					inque[v]=1;
				}
			}
		}
	}
	if(pre[t]!=-1)return true;
	return false;
}
int maxflow,mincost;
void mcmf(int s,int t){
	maxflow=0;
	mincost=0;
	while(spfa(s,t)){
		int u=t;
//		printf("maxflow==%d %d %d\n",maxflow,flow[t],flow[t]*dis[t]);
		maxflow+=flow[t];
		mincost+=flow[t]*dis[t];
		while(u!=s){
			e[last[u]].flow-=flow[t];
			e[last[u]^1].flow+=flow[t];
			u=pre[u];
		}
	}
	return;
}

10.4上下界网络流

咕 咕 咕 待补

11.2-SAT问题

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=2e6+5,inf=0x3f3f3f3f;
int dfn[maxn],z[maxn],vis[maxn],low[maxn],tot,top,t,color[maxn],cnt[maxn];
vector<int>e[maxn];
int n,m; 
inline void add(int u,int v){
	e[u].emplace_back(v);
}
void tarjan(int u){
	dfn[u]=low[u]=++tot;
	z[++top]=u;//入栈 
	vis[u]=1;
	for(auto v:e[u]){
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v]){
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u]){
		t++;//连通分量的标号 
		do{
			color[z[top]]=t;    //属于这个连通分量 
			cnt[t]++;         //记录这个环中有多少个点 
			vis[z[top]]=0; 
			top--;              //出栈 
		}while(u!=z[top+1]);
	}
}
signed main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y,a,b;
		scanf("%d%d%d%d",&x,&a,&y,&b);
		if(a&&b)   add(x+n,y),  add(y+n,x);
		if(!a&&b)  add(x,y),    add(y+n,x+n);
		if(a&&!b)  add(x+n,y+n),add(y,x);
		if(!a&&!b) add(x,y+n),  add(y,x+n);
	}
	for(int i=1;i<=2*n;i++)
		if(!dfn[i]) tarjan(i);
	for(int i=1;i<=n;i++){
		if(color[i]==color[i+n]){
			puts("IMPOSSIBLE");
			return 0;
		}	
	}	
	puts("POSSIBLE");
	for(int i=1;i<=n;i++){
		printf("%d%c",color[i]<color[i+n],i==n?'\n':' ');	//Tarjan 求得的强连通分量的标号为拓扑逆序,即反向的拓扑序 
	}
	return 0;
}

12.杂项

12.1图的染色

12.2欧拉图

定义¶
通过图中所有边恰好一次且行遍所有顶点的通路称为欧拉通路。
通过图中所有边恰好一次且行遍所有顶点的回路称为欧拉回路。
具有欧拉回路的无向图或有向图称为欧拉图。
具有欧拉通路但不具有欧拉回路的无向图或有向图称为半欧拉图。
有向图也可以有类似的定义。
非形式化地讲,欧拉图就是从任意一个点开始都可以一笔画完整个图,半欧拉图必须从某个点开始才能一笔画完整个图。

12.3哈密顿图

定义¶
通过图中所有顶点一次且仅一次的通路称为哈密顿通路。
通过图中所有顶点一次且仅一次的回路称为哈密顿回路。
具有哈密顿回路的图称为哈密顿图。
具有哈密顿通路而不具有哈密顿回路的图称为半哈密顿图。

12.4拆点

网络流题目中的常见处理点限制方法

12.5判环

J o h n s o n Johnson Johnson全源最短路的过程中就用到了这一段代码

在跑 s p f a spfa spfa的过程中,若有点的 入队次数 > = n >=n >=n 即有负环

bool spfa(int s){
	queue<int>q;
	memset(h,63,sizeof(h));
	h[s]=0,vis[s]=1;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(h[v]>h[u]+e[i].w){
				h[v]=h[u]+e[i].w;
				if(!vis[v]){
					vis[v]=1;
					q.push(v);
					cs[v]++;
					if(cs[v]==n)return false;
				}
			}
		}
	}
	return true;
}

12.6 无向图的最小环

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=105,inf=0x3f3f3f3f;
int mp[maxn][maxn],dis[maxn][maxn];
int n,m;
int main(){
	cin>>n>>m;
	memset(mp,inf,sizeof mp);
	memset(dis,inf,sizeof dis);
	for(int i=1;i<=m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		mp[u][v]=mp[v][u]=w;
		mp[u][v]=mp[v][u]=dis[u][v]=dis[v][u]=min(dis[u][v],w);
	}
	int ans=inf;
	for(int k=1;k<=n;k++){
		for(int i=1;i<=n;i++){
			for(int j=i+1;j<=n;j++){
				ans=(int)min((long long)ans,(long long)dis[i][j]+mp[j][k]+mp[k][i]);
			}
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
			}
		}
	}
	if(ans!=inf)cout<<ans;
	else puts("No solution.");
}

13.更高级的图论

你是否和当年萌新的我一样以为网络流就是图论最难的内容了?

13.1 弦图

弦图-OI Wiki

13.2 圆方树

圆方树-OI Wiki

13.3 斯坦纳树

斯坦纳树-OI Wiki

13.4 动态树

1.Link Cut Tree
2.Euler Tour Tree
3.Top Tree

13.5 图论终极大BOSS-动态仙人掌

如果一张无向连通图的每条边最多在一个环内,则称它是一棵 仙人掌 (Cactus) 。多棵仙人掌可以组成沙漠 。

模板题- UOJ-动态仙人掌

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值