一些图论模板

拓扑排序

一种基于 \(DAG\) 图的 \(O(n)\) 遍历图的算法,喜欢结合优先队列反向建图进行考察。

tarjan强连通

补充

stack<int>q;
void tarjan(int pos){
	dfn[pos]=low[pos]=++cnt;//路径序号和所能到达的最小序号
	bk[pos]=1;//标记:是否在栈中
	q.push(pos);//入栈
	for(int i=head[pos];i;i=e[i].last){
		int to=e[i].to;
		if(!dfn[to]){
			tarjan(to);//1.向前走
			low[pos]=min(low[pos],low[to]);//回溯更新
		}
		else if(bk[to])//不在栈中与我无关
			low[pos]=min(low[pos],low[to]);//找到环起点
	}
	if(dfn[pos]==low[pos]){
		col[pos]=++spct;//染色为超级点
		bk[pos]=0;//出栈去标记
		while(!q.empty() && q.top()!=pos){
			int t1=q.top();q.pop();
			col[t1]=spct;
			bk[t1]=0;
		}//出栈去标记
      q.pop();//出栈去标记
	}
	return;
}
int main()
{
	for(int i=1;i<=n;i++)
		if(!dfn[i])tarjan(i);//不一定是连通图
	return 0;
}

UPD on 2022.10.16

简单至上

void tarjan(int u){
	dfn[u]=low[u]=++cnt;bk[u]=1;q.push(u);
	for(int i=head[u];i;i=last[i]){
		int v=to[i];
		if(!dfn[v]){
			tarjan(v);//1.向前走
			low[u]=min(low[u],low[v]);//回溯更新
		}
		else if(bk[v])/*不在栈中与我无关*/low[u]=min(low[u],low[v]);//找到环起点
	}
	if(dfn[u]==low[u]){++spct;
		while(q.top()!=u){
			int now=q.top();q.pop();bk[now]=0;
			col[now]=spct;
		}//出栈去标记
		bk[pos]=0;q.pop();//出栈去标记
		col[u]=spct;//染色为超级点
	}
	return;
}

tarjan割点

UDP on 2022.10.16这篇相当好

void tarjan(int pos){
	dfn[pos]=low[pos]=++cnt;//low为绕到的最早的点
	int child=0;//树根的儿子数量
	for(int i=head[pos];i;i=e[i].last){
		int to=e[i].to;
		if(!dfn[to]){
			child++;
			tarjan(to);
			low[pos]=min(low[pos],low[to]);
			if(low[to]>=dfn[pos] && root!=pos){
				mark[pos]=1;//判断为割点
			}
		}
		low[pos]=min(low[pos],dfn[to]);//必须用dfn
	}
	if(root==pos && child>=2)
		mark[pos]=1;//判断为割点
	return;
}

2-SAT

给出一个集合,集合中有 \(n\)bool 型数。
给出 \(m\) 个关系,表示 \(p1\)\(f1\)\(p2\)\(f2\) 必须满足一个。

那么对每个数值为1或0,分别建点,如果一个关系前者不满足,则后者一定满足。
所以可以建边 \((u,v)\) 表示当u成立时v一定成立。形成一张图后,对图进行缩点,如果一个数的两个值的点在同一个环中,则不成立,因为一个数只有一个值。

求一种可行方案,只要选每个数对应点中的scc中拓扑序大的值即可。
然后tarjan缩点后本来就是一个逆拓扑序的图。

n=read(),m=read();
	for(int i=1;i<=m;i++){
		p1=read(),f1=read(),p2=read(),f2=read();
		add(p1+(f1^1)*n,p2+f2*n),add(p2+(f2^1)*n,p1+f1*n);
	}
	for(int i=1;i<=n*2;i++)if(!dfn[i])tarjan(i);
	for(int i=1;i<=n;i++){
		if(col[i]==col[i+n]){
			printf("IMPOSSIBLE\n");
			return 0;
		}
	}
	printf("POSSIBLE\n");
	for(int i=1;i<=n;i++){
		printf("%d ",(col[i+n]<col[i]));
	}

链式前向星

用链表存边(双向*2)

vector

用动态数组存点P能到的其他点

struct v{
	int to,w;
};
vector<v>g[N];//N为点的数量

YCE说一个vector占20空间。

floyd

f[i][j]在枚举到k时的意思

在能绕第k个点是i到j的最短路

for(int k=1;k<=n;k++)//枚举绕第几个点
	for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++)
      		f[i][j]=min(f[i][j],f[i][k]+f[k][j])

dfs

基于栈的一种搜索

bfs

基于队列的搜索

dijikstra

\(O(m \log_2^n)\)

m为边,n为点。

每次贪心查找离起点最近的点,将其能到的点加入优先队列

struct node{
	int dis;int pos;
	bool operator <(const node &x )const{
		return x.dis<dis;//重载运算符
	}
};
priority_queue<node>q;
void dijkstra()
{
	while(!q.empty()){
		node t1=q.top();
		q.pop();
      for(int i=head[t1.pos];i;i=e[i].last)
      		if(dis[e[i].to]>dis[t1.pos]+e[i].w){
            dis[e[i].to]=dis[t1.pos]+e[i].w;
            q.push((node){dis[e[i].to],e[i].to});
          }
	}
	return;
}
int main()
{
	for(int i=1;i<=n;i++)
		dis[i]=0x3f3f3f3f;
	dis[s]=0;
	q.push((node){0,s});
	dijkstra();
	return 0;
}

spfa

\(O(km)\)

k为每个点平均进队次数,m为边数。

像bfs那样广度拓展

void spfa(){
	while(!q.empty()){
		int t1=q.front();
		q.pop();
      for(int i=head[t1];i;i=e[i].last)
      		if(dis[e[i].to]>dis[t1.pos]+e[i].w){
            dis[e[i].to]=dis[t1.pos]+e[i].w;
            q.push(e[i].to);
          }
	}
   return 0;
}

双端队列优化
其实是一种贪心优化
把距离近的点(比队首)先跑

void spfa(){
    while(!q.empty()){
        ll t=q.front();
        q.pop_front();
        for(ll i=head[t];i;i=e[i].last){
            if(dis[e[i].to]>dis[t]+e[i].w){
                dis[e[i].to]=dis[t]+e[i].w;
                if(!q.empty() && dis[e[i].to<dis[q.front()])q.push_front(e[i].to);
                 else q.push_back(e[i].to);
            }
        }
    }
    return;
}

spfa判负环

  1. 为什么打标记,因为不打标记会导致一些已经进入队列的点重复进入,不会影响最短路的答案(效率也没什么影响),但对进队次数的统计会影响。
  2. 是进队次数\(\ge n\)
void spfa(){
	queue<int>q;
	memset(dis,0x3f,sizeof(dis));
	vis[1]=1;q.push(1);cnt[1]++;dis[1]=0;
	while(!q.empty()){
		int u=q.front();q.pop();vis[u]=0;
		for(int i=head[u];i;i=last[i]){int v=to[i];
			if(dis[v]>dis[u]+w[i]){
				dis[v]=dis[u]+w[i];
				if(!vis[v]){
					vis[v]=1;q.push(v);cnt[v]++;
					if(cnt[v]>=n){can=1;return;}
				}
			}
		}
	}
	return;
}

spfa 搜索判负环
UPD on 2022.10.19 别看了,这可能是错的。

void spfa(int t1){
	cnt[t1]++;
	if(cnt[t1]>=n){
		flag=1;
		return;
	}
	for(int i=head[t1];i;i=last[i]){
		if(dis[to[i]]>dis[t1]+w[i]){
			dis[to[i]]=dis[t1]+w[i];
			spfa(to[i]);
		}
	}
	return;
}

此外,在做P3199 [HNOI2009]最小圈的时候,发现了一种新奇的求负环方法(经过我的反复测试,它确实快,但洛谷模板有个点死活过不去)。目前认为它只能求全图负环(但也不会死循环吧)。总之慎用。

void dfs(int u,double key){
	if(can)return;
	vis[u]++;
	for(int i=head[u];i;i=last[i]){int v=to[i];
		if(dis[v]>dis[u]+w[i]){
			dis[v]=dis[u]+w[i];
			if(vis[v]){
				can=1;
				return;
			}
			dfs(v,key);
		}
	}
	vis[u]=0;
}
int main(){
     can=0;memset(vis,0,sizeof(vis));for(int i=1;i<=n;i++)dis[i]=0;
     for(int s=1;s<=n;s++){
   	 dfs(s,key);
     }
     return 0;
}

最小生成树(Kruskal)

贪心的找最小的边加入队列
适合边少的图

sort(e+1,e+m+1,cmp);
	for(int i=1;i<=n;i++)
		f[i]=i;
	for(int i=1;i<=m;i++)
	{
		if(check(e[i].v)!=check(e[i].m))
		{
			tot++;
			ans+=e[i].w;
			f[check(e[i].v)]=check(e[i].m);
		}
		if(tot==n-1)break;
	}

(Prim)

每次把目前能到的点中的合法最小边选进来
然后再把边终点的其他边加入集合
适合边多的图

void prim(){
	ans=0;
	bk[1]=1;
	for(int i=2;i<=n;i++)
		lowcost[i]=dis[1][i],bk[i]=0;
	for(int i=2;i<=n;i++){
		int min=INT_MAX,id;
		for(int j=1;j<=n;j++)
			if(!bk[j] && lowcost[j]<min){
				min=lowcost[j];
				id=j;
			}
		ans+=min;
		bk[id]=1;
		for(int j=1;j<=n;j++)
			if(!bk[j] && lowcost[j]>dis[id][j])
				lowcost[j]=dis[id][j];
	}
	return;
}

并查集

联通性的判断

int check(int x)
{
    if(a[x]==x)return x;
    return a[x]=check(a[x]);
        
}

按秩合并

也就是可持久化并查集中常用的合并方式!其实也就是一种类似于启发式合并的方式,每一次合并时选择一个深度小的点向深度大的合并。

可持久化并查集

用按秩合并,用可持久化线段树分别维护fa数组和dep数组即可

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+110;
int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9' || c<'0'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c>='0' && c<='9'){
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar(); 
	}
	return x*f;
}
struct node{
	int rson,lson,l,r,fa;
}e[N<<5];
struct DP{
	int rson,lson,l,r,dep;
}h[N<<5];
int n,m,cnt,cnt2,root[N],root2[N];
void buildFA(int &u,int l,int r){
	u=++cnt;
	e[u].l=l;
	e[u].r=r;
	if(l==r){
		e[u].fa=l;
		return;
	}
	int mid=(l+r)>>1;
	buildFA(e[u].lson,l,mid);
	buildFA(e[u].rson,mid+1,r);
	return;
}
void buildDEP(int &u,int l,int r){
	u=++cnt2;
	h[u].l=l;
	h[u].r=r;
	if(l==r){
		h[u].dep=1;
		return;
	}
	int mid=(l+r)>>1;
	buildDEP(h[u].lson,l,mid);
	buildDEP(h[u].rson,mid+1,r);
	return;
}
void updateFA(int pre,int &u,int x,int va){
	if(!u){
		u=++cnt;
		e[u].l=e[pre].l;e[u].r=e[pre].r;
	}
	if(e[pre].l==e[pre].r){
		e[u].fa=va;
		return;
	}
	int mid=(e[u].l+e[u].r)>>1;
	if(x<=mid){
		e[u].rson=e[pre].rson;
		updateFA(e[pre].lson,e[u].lson,x,va);
	}
	else{
		e[u].lson=e[pre].lson;
		updateFA(e[pre].rson,e[u].rson,x,va);
	}
	return;
}
void updateDEP(int pre,int &u,int x,int va){
	if(!u){
		u=++cnt2;
		h[u].l=h[pre].l;h[u].r=h[pre].r;
	}
	if(h[pre].l==h[pre].r){
		h[u].dep=va;
		return;
	}
	int mid=(h[u].l+h[u].r)>>1;
	if(x<=mid){
		h[u].rson=h[pre].rson;
		updateDEP(h[pre].lson,h[u].lson,x,va);
	}
	else{
		h[u].lson=h[pre].lson;
		updateDEP(h[pre].rson,h[u].rson,x,va);
	}
	return;
}
int FA(int u,int x){
	if(e[u].l==e[u].r)return e[u].fa;
	int mid=(e[u].l+e[u].r)>>1;
	if(x<=mid)return FA(e[u].lson,x);
	else return FA(e[u].rson,x);
}
int check(int x,int time){
	int _fa=FA(root[time],x);
	if(x==_fa)return x;
	else return check(_fa,time);
}
int DEP(int u,int x){
	if(h[u].l==h[u].r)return h[u].dep;
	int mid=(h[u].l+h[u].r)>>1;
	if(x<=mid)return DEP(h[u].lson,x);
	else return DEP(h[u].rson,x);
} 
int main(){
	n=read(),m=read();
	int opt,a,b;
	buildFA(root[0],1,n);
	buildDEP(root2[0],1,n);
	for(int i=1;i<=m;i++){
		opt=read();
		if(opt==1){
			a=read(),b=read();
			int A=check(a,i-1);
			int B=check(b,i-1);
			int depA=DEP(root2[i-1],A);
			int depB=DEP(root2[i-1],B);
			if(depA>=depB){
				updateDEP(root2[i-1],root2[i],A,max(depA,depB+1));
				updateFA(root[i-1],root[i],B,A);
			} 
			else{
				updateDEP(root2[i-1],root2[i],B,max(depB,depA+1));
				updateFA(root[i-1],root[i],A,B);
			}
		}
		if(opt==2){
			a=read();
			root[i]=root[a],root2[i]=root2[a];
		}
		if(opt==3){
			a=read(),b=read();
			int A=check(a,i-1);
			int B=check(b,i-1);
			if(A==B)printf("1\n");
			else printf("0\n");
			root[i]=root[i-1];
			root2[i]=root2[i-1];
		}
	}
	return 0;
}
posted @ 2023-03-19 13:37  FJOI  阅读(21)  评论(0)    收藏  举报