Tarjan

无向图

zzy的课件

割边

如果一个无向图中,如果删去一个边后,这条边就是割边,即为桥。
如果一个图中不存在割边,就成这张图为边双连通图,
一个图的极大边双连通图成为边双连通分量
\(dfn\) 是一个点的时间戳(\(dfs\) 序),\(low\) 是指一个点不经过这个点父亲指向它的边所能到达的编号最小的点
\((x,y)\) ,当且仅当 \(y\)\(x\) 搜索树中的一点,且 \(dfn[x]<low[y]\)
因为显然此时 \(x\) 的搜索树中没有点能够走到 \(x\) 的上面,若断开 \((x,y)\) 则至少有 \(x\)\(y\) 不连通。

inline void Tarjan(int x, int fa)
{
	low[x] = dfn[x] = ++id;
	
	for (int i = he[x]; i; i = e[i].nxt)
	{
		int x = e[i].to;
		if (!dfn[v])
		{
			Tarjan(v, x);
			low[x] = min(low[x], low[v]);
			if (dfn[x] < low[v])
			{
				bridge[i] = bridge[i ^ 1] = 1;
			}
		}
		else if (v != fa)
		{
			low[x] = min(low[x], dfn[v]);
		}
	}
}

求边双

void tarjan(int x, int lst)
{
  low[x] = dfn[x] = ++idx;
  st.push(x);
  for (int i = head[x]; i; i = edges[i].nxt)
  {
    if (edges[i].id == (lst ^ 1))
      continue;
    int to = edges[i].to;
    if (!dfn[to])
    {
      tarjan(to, edges[i].id);
      low[x] = min(low[x], low[to]);
    }
    else
    {
      low[x] = min(low[x], dfn[to]);
    }
  }
  if (dfn[x] == low[x])
  {
    vector<int> vec;
    vec.push_back(x);
    while (st.top() != x)
    {
      vec.push_back(st.top());
      st.pop();
    }
    st.pop();
    ans.push_back(vec);
  }
}

割点

如果在一个无向图中删去一个点,这个图不连通,则称这个点是割点,一个无割点的图称为点双连通图,一个图的极大点双连通图成为点双连通分量。
如果一个点是割点,当且仅当它的搜索树中点 \(y\)\(low\) 值均大于等于它,即 \(dfn[x]\le low[y]\)

特别的,若 \(x\) 为根节点,则至少应有两个它的子节点满足上述条件。

void tarjan(int now)
{
  dfn[now] = low[now] = ++id;
  int son = 0;
  for (int i = head[now]; i; i = edges[i].nxt)
  {
    int to = edges[i].to;
    if (!dfn[to])
    {
      son++;
      tarjan(to);
      low[now] = min(low[now], low[to]);
      if (low[to] >= dfn[now] && now != root && !ans[now])
      {
        ++cnt;
        ans[now] = true;
      }
    }
    else
    {
      low[now] = min(low[now], dfn[to]);
    }
  }
  if (!ans[now] && now == root && son >= 2)
  {
    ++cnt;
    ans[now] = true;
  }
}

求点双

一个点双里会有割点,并且一个割点可能会同时存在于不同的点双中

求点双:

void tar(int x, int f)
{
	dfn[x] = low[x] = ++id;
	st[++top] = x;
	int son = 0;
	for (int i = he[x]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			son++;
			tar(v, x);
			low[x] = min(low[x], low[v]);
			if (low[v] >= dfn[x])
			{
				++cnt;
				while (st[top + 1] != v)
				{
					ans[cnt].push_back(st[top--]);
				}
				ans[cnt].push_back(x);
			}
		}
		else if (v != f)
		{
			lox[x] = min(low[x], dfn[v]);
		}
	}
	if (son == 0 && f == 0)
	{
		cnt++;
		ans[cnt].push_back(x);
	}
}

缩点/缩边

注:此篇所有代码都摘自此处
对于一张无向图,我们考虑边双缩点和点双缩点。
边双缩点比较好做,只需要将每个边双连通分量通过桥连起来就好了。
不过点双需要特殊考虑,因为割点是必经点,所以我们需要让每个点双和它内部的割点连边。
缩点时枚举每条边进行重建边即可。
所以我们可以简单记忆成:每个边双之间是通过桥连接起来的,而每个点双则是通过割点连接起来的。
其实不难发现对点双和边双缩完点之后必然会构成一棵树。
所以一般来说,缩点会和树上知识相结合进行考察
缩点:

const int N=1e4+100;
 
const int M=1e5+100;
 
struct Egde
{
	int to,next;
}edge1[M],edge2[M];
 
int head1[N],head2[N],low[N],dfn[N],c[N],num,cnt1,cnt2,dcc,n,m;
 
bool bridge[M];
 
void addedge1(int u,int v)
{
	edge1[cnt1].to=v;
	edge1[cnt1].next=head1[u];
	head1[u]=cnt1++;
}
 
void addedge2(int u,int v)
{
	edge2[cnt2].to=v;
	edge2[cnt2].next=head2[u];
	head2[u]=cnt2++;
}
 
void tarjan(int u,int in_edge)
{
	dfn[u]=low[u]=++num;
	for(int i=head1[u];i!=-1;i=edge1[i].next)
	{
		int v=edge1[i].to;
		if(!dfn[v])
		{
			tarjan(v,i);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u])
				bridge[i]=bridge[i^1]=true;
		}
		else if(i!=(in_edge^1))
			low[u]=min(low[u],dfn[v]);
	}
}
 
void dfs(int u)
{
	c[u]=dcc;
	for(int i=head1[u];i!=-1;i=edge1[i].next)
	{
		int v=edge1[i].to;
		if(c[v]||bridge[i])
			continue;
		dfs(v);
	}
}
 
void solve()
{
	for(int i=1;i<=n;i++)//找桥 
		if(!dfn[i])
			tarjan(i,0);
	for(int i=1;i<=n;i++)//缩点 
		if(!c[i])
		{
			dcc++;
			dfs(i);
		}
}
 
void build()//缩点+连边 
{
	solve();
	for(int i=2;i<cnt1;i+=2)
	{
		int u=edge1[i^1].to;
		int v=edge1[i].to;
		if(c[u]==c[v])
			continue;
		addedge2(c[u],c[v]);
		addedge2(c[v],c[u]);
	}
}
 
void init()
{
    cnt1=2;
	cnt2=num=dcc=0;
	memset(head2,-1,sizeof(head2));
	memset(head1,-1,sizeof(head1));
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	memset(bridge,false,sizeof(bridge));
	memset(c,0,sizeof(c));
}

缩边:

const int N=1e4+100;
 
const int M=1e5+100;
 
struct Egde
{
	int to,next;
}edge1[M],edge2[M];
 
int head1[N],head2[N],low[N],dfn[N],c[N],Stack[N],new_id[N],num,cnt,cnt1,cnt2,tot,root,top,n,m;
 
bool cut[N];
 
vector<int>dcc[N];
 
void addedge1(int u,int v)
{
	edge1[cnt1].to=v;
	edge1[cnt1].next=head1[u];
	head1[u]=cnt1++;
}
 
void addedge2(int u,int v)
{
	edge2[cnt2].to=v;
	edge2[cnt2].next=head2[u];
	head2[u]=cnt2++;
}
 
void tarjan(int u)
{
	dfn[u]=low[u]=++num;
	Stack[++top]=u;
	if(u==root&&head1[u]==-1)
	{
		dcc[++cnt].push_back(u);
		return;
	}
	int flag=0;
	for(int i=head1[u];i!=-1;i=edge1[i].next)
	{
		int v=edge1[i].to;
		if(!dfn[v])
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u])
			{
				flag++;
				if(u!=root||flag>1)
					cut[u]=true;
				cnt++;
				int x;
				do
				{
					x=Stack[top--];
					dcc[cnt].push_back(x);
				}while(x!=v);
				dcc[cnt].push_back(u);
			}
		}
		else
			low[u]=min(low[u],dfn[v]);
	}
}
 
void solve()
{
	for(int i=1;i<=n;i++)//找割点+缩点 
		if(!dfn[i])
		{
			root=i;
			tarjan(i);
		}
}
 
void build()//缩点+连边 
{
	solve();
	num=cnt;
	for(int i=1;i<=n;i++)
		if(cut[i])
			new_id[i]=++num;
	for(int i=1;i<=cnt;i++)
		for(int j=0;j<dcc[i].size();j++)
		{
			int x=dcc[i][j];
			if(cut[x])
			{
				addedge2(i,new_id[x]);
				addedge2(new_id[x],i);
			}
			else
				c[x]=i;
		}
}
 
void init()
{
	for(int i=0;i<N;i++)
		dcc[i].clear();
	cnt=cnt2=cnt1=num=tot=top=0;
	memset(head2,-1,sizeof(head2));
	memset(head1,-1,sizeof(head1));
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	memset(cut,false,sizeof(cut));
	memset(c,0,sizeof(c));
	memset(Stack,0,sizeof(Stack));
	memset(new_id,0,sizeof(new_id));
}

有向图

zzy的课件

强连通分量

对于一有向图中任意两点 \(x,y\) 既有 \(x\)\(y\) 的路径,又有 \(y\)\(x\) 的路径,则称该图为强连通图。
极大的强连通图为强连通分量

void tar(int x)
{
	dfn[x] = low[x] = ++id;
	stk.push(x);
	vis[x] = true;
	for (int i = head[x]; i; i = edges[i].nxt)
	{
		int to = edges[i].to;
		if (!dfn[to])
		{
			tar(to);
			low[x] = min(low[x], low[to]);
		}
		else if (vis[to])
		{
			low[x] = min(low[x], dfn[to]);
		}
	}
	if (dfn[x] == low[x])
	{
		top++; // 颜色
		int y;
		do
		{
			y = stk.top();
			stk.pop();
			vis[y] = 0;
			col[y] = top;
		}while (x != y);
	}
}

缩点:

for (int x = 1; x <= n; ++x)
{
	for (int i = head[x]; i; i = edges[i].nxt)
	{
		int to = edges[i].to;
		if (col[x] != col[to])
		{
			Add(col[x], col[to]); // 为颜色之间建边
		}
	}
}

2-SAT

对若干个变量,每个变量有两种取值,并且包含若干条限制,形如若 \(A[x]\)\(p\)\(A[y]\)\(q\)
做法:并查集
image
image
image
代码请看这里

二分图

image

匈牙利算法

当我们找不到一条增广路时,则我们找到了最大匹配
image

bool dfs(int x)
{
	for (int i = head[i]; i; i = edges[i].nxt)
	{
		int to = edges[i].to;
		if (!vis[to])
		{
			vis[to] = true;
			if (!res[to] || dfs(res[v])) // //如果暂无匹配,或者原来匹配的左侧元素可以找到新的匹配
			{
				res[v] = x;
				return 1;
			}
		}
	}
	return 0;
}
for (int i = 1; i <= n; ++i)
{
	memset(vis, 0, sizeof (vis));
	if (!dfs(i))
	{
		ans++;
	}
}

题单

posted @ 2024-12-15 09:28  SigmaToT  阅读(13)  评论(0)    收藏  举报