图论相关总结

最短路

普通

对于一般的最短路,较为常用的有spfa算法和dijstra算法。大多数情况下,spfa更常用,但对于稠密图,spfa可能会被卡爆。不多说,上代码
spfa(使用前向星):

void spfa(){
	queue<int>q;
	q.push(S);
	memset(dis,0x3f,sizeof(dis));
	dis[S]=0;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(dis[v]>dis[u]+e[i].w){
				dis[v]=dis[u]+e[i].w;
				if(!vis[v]){//判负环时标记每个点进队次数,cnt[i]>n-1就有负环
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
}

dijkstra(堆优化):

oid dijkstra(){
	memset(dis,0x3f,sizeof(dis));
	dis[S]=0;
	priority_queue<Node>q;//需重载运算符
	q.push((Node){S,0});
	while(!q.empty()){
		int u=q.top().now;
		q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(!vis[v]&&dis[v]>dis[u]+e[i].w){
				dis[v]=dis[u]+e[i].w;
				q.push((Node){v,dis[v]});
			}
		}
	}
}

差分约束

题目会给出一些不等关系,结论: 对于每个不等式 x[i] - x[j] <= a[k],对结点 j 和 i 建立一条 j -> i的有向边,边权为a[k], 求x[n-1] - x[0] 的最大值就是求 0 到n-1的最 短路。
例:奶牛的站位Layout(usaco2005dec)

#include<bits/stdc++.h>
using namespace std;
const int N=1005;
const int M=10005;
int n,F,E,cnt,head[N],dis[N],vis[N],ti[N];
int flag=0;
struct Node{
	int u,v,nxt,w;
}e[M<<1];
inline void add(int u,int v,int w){
	e[++cnt].u=u;
	e[cnt].v=v;
	e[cnt].w=w;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
inline void spfa(int s){
	memset(dis,0x3f,sizeof(dis));
	dis[s]=0;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(dis[v]>dis[u]+e[i].w){
				dis[v]=dis[u]+e[i].w;
				if(!vis[v]){
					vis[v]=1;
					ti[v]++;
					if(ti[v]==n-1){
						flag=1;
						return;
					}
					q.push(v);
				}
			}
		}
	}
}
int main(){
	scanf("%d%d%d",&n,&F,&E);
	for(int i=1;i<=F;i++){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		if(u>v)
			swap(u,v);
		add(u,v,w);
	}
	for(int i=1;i<=E;i++){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		if(u<v)
			swap(u,v);
		add(u,v,-w);
	}
	spfa(1);
	if(!flag&&dis[n]==0x3f3f3f3f)
		printf("-1");
	else if(flag)
		printf("-2");
	else
		printf("%d",dis[n]);
	return 0;
}

次短路

从起点和终点分别向对面跑最短路,得到dis1数组和dis2数组,枚举每个点,取dis1[i]+dis2[i]>最短路中最小的。

分层图

如果在跑图时有状态变化,比如拿到钥匙,获得buff之类的,可以考虑建分层图。
例:走迷宫(usaco2015dec)

/*
f数组将地图分为两层,第三维1是臭气层,0是正常层 
*/
#include<bits/stdc++.h>
using namespace std;
int g[1200][1200]={},f[1005][1005][3]={};
int n,m,inf=0x3f3f3f3f;
int dx[5]={0,0,0,1,-1};
int dy[5]={0,1,-1,0,0};
struct Node{
	int x,y,d,se;//x,y是位置,d是步数,se是层数,也就是有没有味道 
};
queue<Node>q;
int check(int xx,int yy,int s){
	if(!g[xx][yy])
		return 0;
	if(g[xx][yy]==3)//判臭气 
		return s;
	return 1;
}
int bfs(){//广搜,用Node来做队列,直接从里面取 
	q.push((Node){1,1,0,0});
	f[1][1][0]=0;
	while(!q.empty()){
		Node t=q.front();
		q.pop();
		for(int i=1;i<=4;i++){
			int nx=t.x+dx[i],ny=t.y+dy[i];
			int nd=t.d+1;//移动步数 
			int nse=t.se;
			if(!check(nx,ny,t.se))
				continue;
			while(g[nx][ny]==4){//紫色滑到底 
				int tx=nx+dx[i],ty=ny+dy[i];
				if(!check(tx,ty,t.se))
					break;
				nx+=dx[i];
				ny+=dy[i];
				nd++;
				nse=0;
			}
			if(g[nx][ny]==2)//有鳄鱼,nse变成1,去f[][][1]获得臭气 
				nse=1;
			if(f[nx][ny][nse]<=nd)// ↘
				continue;//→→→→→→尝试更新最小值 
			f[nx][ny][nse]=nd;//↗
			q.push((Node){nx,ny,nd,nse});
		}
	}
}
int main(){
	memset(f,0x3f,sizeof(f));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			scanf("%d",&g[i][j]);
	bfs();
	int ans=inf;
	ans=min(f[n][m][0],f[n][m][1]);//从两层的终点找最小值 
	if(ans==inf)
		printf("-1");
	else
		printf("%d",ans);
	return 0;
}

线段树优化建图

这种题题目会说由点指向区间,或区间指向点,而线段树可以很好的解决这个问题而不是一个一个连线。建图时要看清题目条件,而考虑建一棵树还是两棵树。
例:Legacy(From Codeforces)(好题啊)

#include<bits/stdc++.h>
using namespace std;
#define re read()
#define ri register int
#define ll long long 
#define mid ((T1[q].l+T1[q].r)>>1)
#define med ((T2[q].l+T2[q].r)>>1)
#define lc (q<<1)
#define rc (q<<1|1)
inline char nc(){
	return getchar();
	static char buf[10000000],*p1=buf,*p2=buf;
	return p1==p2&&(p2=(p1=buf)+fread(buf,1,10000000,stdin),p1==p2)?EOF:*p1++;
}
inline int read(){
	char ch=nc();
	int res=0;
	while(ch<'0'||ch>'9')
		ch=nc();
	while(ch<='9'&&ch>='0'){
		res=(res<<3)+(res<<1)+ch-'0';
		ch=nc();
	}
	return res;
}
const int N=200005;
int head[N<<1],cnt,n,q,T,S;
int tot,pos1[N],pos2[N];
int U,V,L,R;
ll W,dis[N<<3],vis[N<<3];
struct Node{
	int u,v,nxt;
	ll w;
}e[N<<3];
struct Tree{
	int l,r,num;
}T1[N<<2],T2[N<<2];
inline void add(int u,int v,ll w){
	e[++cnt].v=v;
	e[cnt].u=u;
	e[cnt].w=w;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
inline void build1(int q,int l,int r){//构造下线段树 
	T1[q].num=++tot;
	T1[q].l=l;
	T1[q].r=r;
	if(l==r){
		pos1[l]=T1[q].num;
		return;
	}
	build1(lc,l,mid);
	build1(rc,mid+1,r);
	add(T1[lc].num,T1[q].num,0);
	add(T1[rc].num,T1[q].num,0);
}
inline void build2(int q,int l,int r){
	T2[q].num=++tot;
	T2[q].l=l;
	T2[q].r=r;
	if(l==r){
		pos2[l]=T2[q].num;
		add(pos2[l],pos1[l],0);
		return;
	}
	build2(lc,l,med);
	build2(rc,med+1,r);
	add(T2[q].num,T2[lc].num,0);
	add(T2[q].num,T2[rc].num,0);
}
inline void update_P_to_S(int q,int ql,int qr,ll w){
	if(ql<=T2[q].l&&T2[q].r<=qr){
		add(pos1[U],T2[q].num,w);
		return;
	}
	if(ql<=med)
		update_P_to_S(lc,ql,qr,w);
	if(qr>med)
		update_P_to_S(rc,ql,qr,w);
}
inline void update_S_to_P(int q,int ql,int qr,ll w){
	if(ql<=T1[q].l&&T1[q].r<=qr){
		add(T1[q].num,pos2[V],w);
		return;
	}
	if(ql<=mid)
		update_S_to_P(lc,ql,qr,w);
	if(qr>mid)
		update_S_to_P(rc,ql,qr,w);
}
inline void spfa(){
	queue<int>q;
	q.push(pos1[S]);
	for(ri i=1;i<=n<<3;++i)
		dis[i]=1e18;
	dis[pos1[S]]=0;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(ri i=head[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(dis[u]+e[i].w<dis[v]){
				dis[v]=dis[u]+e[i].w;
				if(!vis[v]){
					q.push(v);
					vis[v]=1;
				}
			}
		}
		vis[u]=0;
	}
}
int main(){
	n=re;q=re;S=re;
	build1(1,1,n);
	build2(1,1,n);
	for(ri i=1;i<=q;++i){
		int op=re;
		if(op==1){
			U=re,V=re,W=re;
			add(pos1[U],pos2[V],W);
		}else if(op==2){//点到区间 
			U=re,L=re,R=re,W=re;
			update_P_to_S(1,L,R,W);
		}else{//区间到点 
			V=re,L=re,R=re,W=re;
			update_S_to_P(1,L,R,W);
		}
	}
	spfa();
	for(ri i=1;i<=n;++i)
		printf("%lld ",dis[pos1[i]]<1e18?dis[pos1[i]]:-1);
	return 0;
}

最小生成树

常见算法有prim和kruskal,而我个人因为更好打而更喜欢kruskal。两个算法本质都是贪心。
代码:
kruskal:

bool cmp(const Node& a,const Node& b){
	return a.w<b.w;
}
int getfa(int x){
	return x==fa[x]?x:fa[x]=getfa(fa[x]);
}
int merge(int x,int y){
	int dx=getfa(x);
	int dy=getfa(y);
	if(dx!=dy){
		fa[dx]=dy;
		size[dy]+=size[dx];
		return 1;
	}
	return 0;
}
void kruskal(){
	sort(e+1,e+m+1,cmp);
	for(ri i=1;i<=m;++i){
		int u=e[i].u,v=e[i].w;
		if(merge(u,v)){
			ans+=e[i].w;
		}
	}
}

prim(其实可以堆优化,但这里没打):

int prime(int start=1){
	int k=0,ans=0;
	for(int i=1;i<=n;i++){
		mincost[i]=g[start][i];
	}
	mincost[start]=0;
	for(int i=1;i<n;i++){
		int mi=inf;
		for(int j=1;j<=n;j++){
			if(mincost[j]!=0&&mi>mincost[j]){
				k=j;
				mi=mincost[j];
			}
		}
		ans+=mi;
		mincost[k]=0;
		for(int j=1;j<=n;j++){
			if(mincost[j]!=0&&g[k][j]<mincost[j]){
				mincost[j]=g[k][j];
			}
		}
	} 
	return ans;
}

拓扑排序

记录每个点的入度,每次有其他访问到时将其减一,为零时可入队
代码:

void topsort(){
	queue<int>q;
	for(ri i=1;i<=n;++i)
		if(!du[i])
			q.push(i);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		x[++tot]=u;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;
			--du[v];
			if(!du[v])
				q.push(v);
		}
	}
}

图的连通性

tarjan算法,记录dfs序和low数组,low数组表示该点能回到的最小的dfs序编号
无向图求割边,割点:
例:网络(CEOI1996)

void tarjan(int u,int fa){
	int son=0;//儿子,特判 
	dft[u]=low[u]=++tot;
	for(int i=f[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(!dft[v]){
			son++;
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dft[u]){
				flag[u]=1;
			}
		}
		else if(v!=fa){
			low[u]=min(low[u],dft[v]);
			}
		if(u==root&&son==1){//特判,如果是根且只有一个儿子 
			flag[u]=0;
		}
	}	
}

有向图求强连通分量
例:[HAOI2006]受欢迎的牛

void dfs_tarjan(int u){
	low[u]=dfn[u]=++num;
	sta.push(u);
	vis[u]=1;
	for(int i=f[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(!dfn[v]){
			dfs_tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v]){
				low[u]=min(low[u],dfn[v]);
			}
	}
	if(dfn[u]==low[u]){
		int t;
		cnt_color++;
		while(1){
			t=sta.top();
			sta.pop();
			color[t]=cnt_color;
			size[cnt_color]++;
			if(t==u){
				break;
			}
		}
	}
}

网络流及费用流

只会dinic算法。。。
有一道题叫网络扩容,既用到了网络流,有用到了费用流。先跑网络流,在此基础上每条边再加上一条有费用的边再跑费用流。
例:网络扩容(zjoi2010)

#include<bits/stdc++.h>
using namespace std;
const int SS=0;
const int N=1005;
const int M=5005;
int S,T,vis[N<<1],dis[N<<1];
int cur[N<<1];
int ans;
int cnt=1,n,m,k,head[N<<1];
int U[M<<2],V[M<<2],W[M<<2];
struct Node{
	int v,w,c,nxt;
}e[M<<3];
inline void add(int u,int v,int c,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].c=c;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
inline int bfs(){
	memset(dis,-1,sizeof(dis));
	dis[S]=0;
	queue<int>q;
	q.push(S);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(dis[v]==-1&&e[i].c>0){
				dis[v]=dis[u]+1;
				q.push(v);
				if(v==T)
					return 1;
			}
		}
	}
	return 0;
}
inline int dfs(int u,int f){
	if(u==T||f==0)
		return f;
	int used=0;
	for(int &i=cur[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(dis[v]==dis[u]+1&&e[i].c>0){
			int w=dfs(v,min(f,e[i].c));
			if(!w)
				continue;
			f-=w;
			e[i].c-=w;
			e[i^1].c+=w;
			used+=w;
			if(f==0)
				break;
		}
	}
	if(used==0)
		dis[u]=-1;
	return used;
}
inline int dinic(){
	int mflow=0;
	while(bfs()){
		memcpy(cur,head,sizeof(head));
		mflow+=dfs(S,0x3f3f3f3f);
	}
	return mflow;
}
inline int spfa(){
	queue<int>q;
	memset(vis,0,sizeof(vis));
	memset(dis,0x3f,sizeof(dis));
	dis[SS]=0;
	q.push(SS);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(dis[v]>dis[u]+e[i].w&&e[i].c>0){
				dis[v]=dis[u]+e[i].w;
				if(!vis[v]){
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
	return dis[T]<dis[2005];
}
inline int dfs2(int u,int f){
	if(u==T||f==0)
		return f;
	int used=0;
	vis[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(!vis[v]&&dis[u]+e[i].w==dis[v]&&e[i].c>0){
			int w=dfs2(v,min(f,e[i].c));
			if(w){
				ans+=e[i].w*w;
				e[i].c-=w;
				e[i^1].c+=w;
				used+=w;
			}
			if(f==used)
				break;
		}
	}
	return used;
}
inline void dinic_fee(){
	while(spfa()){
		memset(vis,0,sizeof(vis));
		dfs2(SS,0x3f3f3f3f);
	}
}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	S=1,T=n;
	for(int i=1;i<=m;i++){
		int u,v,c,w;
		scanf("%d%d%d%d",&u,&v,&c,&w);
		U[i]=u,V[i]=v,W[i]=w;
		add(u,v,c,0);
		add(v,u,0,0);
	}
	printf("%d ",dinic());
	add(SS,S,k,0);
	add(S,SS,0,0);
	for(int i=1;i<=m;i++){
		add(U[i],V[i],k,W[i]);
		add(V[i],U[i],0,-W[i]);
	}
	dinic_fee();
	printf("%d",ans);
	return 0;
}

总结那就先这样吧

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值