Tarjan
无向图
割边
如果一个无向图中,如果删去一个边后,这条边就是割边,即为桥。
如果一个图中不存在割边,就成这张图为边双连通图,
一个图的极大边双连通图成为边双连通分量
\(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));
}
有向图
强连通分量
对于一有向图中任意两点 \(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\)
做法:并查集



代码请看这里
二分图

匈牙利算法
当我们找不到一条增广路时,则我们找到了最大匹配

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++;
}
}

浙公网安备 33010602011771号