CSP图论复习

最短路

dijkstra

O ( ( m + n ) l o g n ) O((m+n)log_n) O((m+n)logn)

基于贪心思想,不能处理负边权(每个点第一次出为最短路,但负边权若离源点较远,还没更新到就已确定了答案)

inline void dijkstra(){
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	dis[1]=0;
	priority_queue<pair<int,int>>q;
	q.push(make_pair(0,1));
	while(!q.empty()){
		int x-q.top();q.pop();
		if(vis[x])continue;
		vis[x]=1;
		for(int i=first[x],v;i;i=e[i].nxt){
			v=e[i].v;
			if(dis[v]>dis[x]+e[i].w){
				dis[v]=dis[x]+e[i].w;
				q.push(make_pair(-dis[v],v));
			}
		}
	}
}

SPFA

复杂度玄学,最坏 O ( n m ) O(nm) O(nm)

判负环: c n t [ x ] cnt[x] cnt[x]表示从1到 n n n的最短路包含的边数, c n t [ 1 ] = 0 cnt[1]=0 cnt[1]=0,若发现 c n t [ v ] > = n cnt[v]>=n cnt[v]>=n则有负环(也可以判一个点入队大于等于 n n n次,但复杂度低些)

别用双端队列优化,卡的更凶,且无法判负环(入队 n n n次也不一定是被更新了 n n n次), d f s dfs dfs版也会被卡

queue<int>q;
inline bool spfa(){
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	q.push(1);
	vis[1]=1;dis[1]=0;
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(int i=first[x],v;i;i=e[i].nxt){
			v=e[i].v;
			if(dis[v]>dis[x]+e[i].w){
				dis[v]=dis[x]+e[i].w;
				cou[v]=cou[x]+1;//!!
				if(cou[v]>=n)return 1;
				if(!vis[v]){
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
	return 0;
}

floyd

任意两点之间最短路, O ( n 3 ) O(n^3) O(n3)

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]);

求传递闭包

关系具有传递性,且传递性推导出尽量多的元素之间的关系,如判有向图,一个点是否能到达另一个点

普通版

for(int k=1;k<=n;k++)
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			b[i][j]|=b[i][k]&b[k][j];

b i t s e t bitset bitset

bitset<N>b[N];
for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
		if(f[j].test(i))f[j]|=f[i];//用f[i]更新f[j]

最小环:
f l o y d floyd floyd中再开一个更新答案

vector<int>path;
inline void get_path(int x,int y){
	if(pox[x][y]==0)return;
	get_path(x,pos[x][y]);
	path.push_back(pos[x][y]);
	get_path(pos[x][y],y);
}
for(int k=1;k<=n;k++){
	for(int i=1;i<k;i++)
		for(int j=i+1;j<k;j++)
			if(dis[i][j]+e[j][k]+a[k][i]<ans){
				ans=dis[i][j]+a[j][k]+a[k][i];
				path.clear();
				path.push_back(i);
				get_path(i,j);
				path.push_back(j);
				path.push_back(k);
			}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(dis[i][j]>dis[i][k]+dis[k][j]){
				dis[i][j]=dis[i][k]+dis[k][j];
				pos[i][j]=k;
			}
}

也可以设 d i s [ i ] [ i ] = i n f dis[i][i]=inf dis[i][i]=inf求完之后就是最小环了

最小生成树

kruskal

O ( m l o g m ) O(mlog_m) O(mlogm)

贪心加入最小的边

inline void find(int x){
	return x==fa[x]?x:fa[x]=find(fa[x]);
}
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1,fu,fv;i<=m;i++){
	fu=find(e[i].u);fv=find(e[i].v);
	if(fu==fv)continue;
	fa[fu]=fv;
	ans+=e[i].w;
}

prim

O ( n 2 ) O(n^2) O(n2)

适用于稠密图

inline void prim(){
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	dis[1]=0;
	for(int i=1,x;i<=n;i++){
		x=0;
		for(int j=1;j<=n;j++)
			if(!vis[j]&&(!x||dis[j]<dis[x]))x=j;
		vis[j]=1;
		for(int j=1;j<=n;j++)
			if(!vis[j])dis[j]=min(dis[j],e[x][j]);
	}
}
for(int i=1;i<=n;i++)ans+=dis[i];

最小树形图

方法:(朱-刘算法)

  1. 求最短弧集合E

  2. 判断集合E中有没有有向环,如果有转步骤3,否则转4

  3. 收缩点,把有向环收缩成一个点,并且对图重新构建,包括边权值的改变和点的处理,之后再转步骤1

  4. 展开收缩点,求得最小树形图

while(1){
	memset(mn,0x3f,sizeof(mn));
	for(int i=1,u,v,w;i<=m;i++){
		u=e[i].u;v=e[i].v;w=e[i].w;
		if(u!=v&&w<mn[v]){
			mn[v]=w;from[v]=u;
		}
	}
	for(int i=1;i<=n;i++)
		if(i!=rt&&mn[i]==inf){
			printf("-1");return (0-0);
		}
	cnt=0;
	memset(vis,0,sizeof(vis));
	memset(cn,0,sizeof(cn));
	for(int i=1,v;i<=n;i++){
		if(i==rt)continue;
		ans+=mn[i];
		v=i;
		while(vis[v]!=i&&!cn[v]&&v!=rt){
			vis[v]=i;
			v=from[v];
		}
		if(!cn[v]&&v!=rt){
			cn[v]=++cnt;
			for(int j=from[v];j!=v;j=from[j])
				cn[j]=cnt;
		}
	}
	if(!cnt)break;
	for(int i=1;i<=n;i++)
		if(!cn[i])cn[i]=++cnt;
	for(int i=1,u,v;i<=m;i++){
		u=e[i].u;v=e[i].v;
		e[i].u=cn[u];e[i].v=cn[v];
		if(cn[u]!=cn[v])e[i].w-=mn[v];//代替之前的边 
	}
	rt=cn[rt];
	n=cnt;
}
printf("%d",ans);

树的直径

  1. 两遍 d f s dfs dfs求出最远点(负边权不适用)
  2. dp求,当前点到叶子节点的最大路径

(树的直径可以作为很多树上问题的突破口)

inline void dp(int x,int fa){
	for(int i=first[x];i;i=e[i].nxt){
		v=e[i].v;
		if(v==fa)continue;
		dp(v,x);
		ans=max(ans,dis[x]+dis[v]+e[i].w);
		dis[x]=max(dis[x],dis[v]+e[i].w);
	}
	if(x==rt)ans=max(ans,dis[x]);
}

最近公共祖先(LCA)

倍增

O ( l o g n ) O(log_n) O(logn)

inline void lca_pre(int x){
	for(int i=1;(1<<i)<=dep[x];i++)
		fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int i=first[x],v;i;i=e[i].nxt){
		v=e[i].v;
		if(dep[v]||v==fa[x][0])continue;
		dep[v]=dep[x]+1;
		fa[v][0]=x;
		lca_pre(v);
	}
}
inline int LCA(int u,int v){
	if(dep[u]<dep[v])swap(u,v);
	int r=dep[u]-dep[v];
	for(int i=0;(1<<i)<=r;i++)
		if(r&(1<<i))u=fa[u][i];
	if(u==v)return u;
	for(int i=19;i>=0;i--)
		if(fa[u][i]!=fa[v][i]){
			u=fa[u][i];v=fa[v][i];
		}
	return fa[u][0];
}

树剖

O ( l o g n ) O(log_n) O(logn)

inline void dfs1(int x){
	dis[x]=dis[fa[x]]+num[x];
	siz[x]=1;//!!!!!
	for(re int i=0,v;i<ne[x].size();i++){
		v=ne[x][i];
		if(v==fa[x])continue;
		dep[v]=dep[x]+1;
		fa[v]=x;
		dfs1(v);
		siz[x]+=siz[v];
		if(siz[v]>siz[son[x]])son[x]=v;
	}
} 
inline void dfs2(int x,int tp){
	dfn[x]=++tot;
	top[x]=tp;
	if(son[x])dfs2(son[x],tp);
	for(re int i=0,v;i<ne[x].size();i++){
		v=ne[x][i];
		if(v==fa[x]||v==son[x])continue;
		dfs2(v,v);
	}
}
inline int LCA(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])u^=v^=u^=v;
		u=fa[top[u]];
	}
	return dep[u]<dep[v]?u:v;
}

tarjan

O ( 1 ) O(1) O(1)

离线算法

把询问存储至每个节点处,若回溯完成则将节点指向父节点, l c a lca lca即为所指向的节点

inline int find(int x){
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
inline void tarjan(int x){
	vis[x]=1;
	for(int i=first[x],v;i;i=e[i].nxt;i++){
		v=e[i].v;
		if(vis[v])continue;
		tarjan(v);
		fa[v]=x;
	}
	for(int i=0,v,id;i<q[x].size();i++){
		v=q[x][i].first;id=q[x][i].second;
		iF(vis[v]==2)lca[id]=find(v);
	}
	vis[x]=2;
}

基环树

  1. 把环看做一个点
  2. 环形处理方法:破环为链(扩两倍),强制环上两点的关系

差分约数

约数条件: x i − x j < = c x_i-x_j<=c xixj<=c变形 x i < = x j + c x_i<=x_j+c xi<=xj+c d i s [ i ] < = d i s [ j ] + c dis[i]<=dis[j]+c dis[i]<=dis[j]+c极其相似,可以从 j j j i i i连一条 c c c的边(若约束过于分散可以加入0号节点)跑最短路即可(注意负环)

> = >= >=可以求最长路

tarjan与无向图连通性

在图论的连通性问题中,我们经常要从搜索树的角度来分析

有时需要可以建”补图“

割边

inline void tarjan(int x,int from){
	dfn[x]=low[x]=++tot;
	for(int i=first[x],v;i;i=e[i].nxt){
		v=e[i].v;
		if(!dfn[v]){
			tarjan(v,i);
			low[x]=min(low[x],low[v]);
			if(low[v]>dfn[x])//!!
				bridge[i]=bridge[i^1]=1;
		}
		else if((i^1)!=from)low[x]=min(low[x],dfn[v]);//!!
	}
}

割点

inline void tarjan(int x){
	dfn[x]=low[x]=++tot;
	int fl=0;
	for(int i=first[x],v;i;i=e[i].nxt){
		v=e[i].v;
		if(!dfn[v]){
			tarjan(v);
			low[x]=min(low[x],low[v]);
			if(low[v]>=dfn[x]){//!!
				fl++;
				if(x!=root||fl>1)cnt[x]=1;//root特判
			}
		}
		else low[x]=min(low[x],dfn[v]);
	}
}

边双联通分量

inline void tarjan(int x,int from){
	dfn[x]=low[x]=++tot;
	st[++ttp]=x;
	for(re int i=first[x],v;i;i=e[i].nxt){
		if((i^1)==from)continue;
		v=e[i].v;
		if(dfn[v])low[x]=min(low[x],dfn[v]);
		else{
			tarjan(v,i);
			low[x]=min(low[x],low[v]);
		}
	}
	if(low[x]==dfn[x]){//缩点
		cn++;int v;
		do{
			v=st[ttp--];
			dcc[v]=cn;
			num[cn]+=a[v];
		}while(v!=x);
	}
} 

也可以先求割边,删去后, d f s dfs dfs各个联通块

点双联通分量

inline void tarjan(int x){
	dfn[x]=low[x]=++tot;
	st[++top]=x;
	if(x==rt&&first[x]==0){//孤立点
		dcc[++cn].push_back(x);
		return;
	}
	int fl=0;
	for(int i=first[x],v,u;i;i=e[i].nxt){
		v=e[i].v;
		if(!dfn[v]){
			tarjan(v);
			low[x]=min(low[x],low[v]);
			if(low[v]>=dfn[x]){
				fl++;
				if(x!=root||fl>1)cnt[x]=1;
				cn++;
				do{//!!
					u=st[top--];
					dcc[cn].push_back(u);
				}while(u!=v);
				dcc[cn].push_back(x);//割点可能在多个点双内
			}
		}
		else low[x]=min(low[x],dfn[v]);
	}
}

缩点后一般将割点与包含它的所有 d c c dcc dcc连边,形成一棵树

欧拉路问题

欧拉回路:一笔画,起点终点相同,所有点度数为偶数

欧拉通路:一笔画,除起点终点度数为奇数外,其余点为偶数

输出具体方案:

inline void dfs(int x){
	for(int i=first[x];i;i=e[i].nxt){
		if(vis[i])continue;
		vis[i]=vis[i^1]=1;
		dfs(e[i].v);
		st[++top]=e[i].v;
	}
}
int main(){
	……
	dfs(s)
	cout<<1<<" ";//??
	for(int i=top;i;i--)//貌似也不用倒序
		cout<<st[i]<<" ";
	return (0-0);
}

tarjan与有向图连通性

有向图的强连通分量

inline void tarjan(int x){
	dfn[x]=low[x]=++tot;
	st[++top]=x;vis[x]=1;
	for(int i=first[x],v;i;i=e[i].nxt){
		v=e[i].w;
		if(!dfn[v]){
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else if(vis[v])low[x]=min(low[x],dfn[v]);//!!
	}
	if(dfn[x]==low[y]){
		cn++;int u;
		do{
			u=st[top--];
			vis[u]=0;
			scc[u]=cn;
			sc[cn].push_back(u);
		}while(u!=x);
	}
}

有向图的必经边必经点

从起点到终点必经的点叫必经点,同理也有必经边

有环的有向图需用支配树来解,这里不讨论

有向无环图的必经点与必经边:

  1. 原图中拓扑序动态规划,起点到每个点的路径条数为 f s [ x ] fs[x] fs[x]
  2. 反图中同理求出终点到每个点的路径条数 f t [ x ] ft[x] ft[x]
  3. 必经边 ( x , y ) (x,y) (x,y)满足 f s [ x ] ∗ f t [ y ] = f s [ T ] fs[x]*ft[y]=fs[T] fs[x]ft[y]=fs[T]
  4. 必经点 x x x满足 f s [ x ] ∗ f t [ x ] = f s [ T ] fs[x]*ft[x]=fs[T] fs[x]ft[x]=fs[T]

2-SAT

我们通俗的说,就是给你 n n n个变量 a i a_i ai,每个变量能且只能取 0 / 1 0/1 0/1的值。同时给出若干条件,形式诸如 ( n o t ) a i o p t ( n o t ) a j = 0 / 1 (not)a_i opt(not) a_j=0/1 (not)aiopt(not)aj=0/1,其中 o p t opt opt表示 a n d , o r , x o r and,or,xor and,or,xor中的一种

我们把每个点拆为两个,用若 x x x y y y的方式连边,同时也要连逆否命题

求解时染色,若矛盾或一个点的两种状态连通则无解,不然可以随便求出一组解

(有时关系不太好描述,如&,可直接制造矛盾)

你可能感兴趣的:(学习小结)