缩点模板
缩点(图的联通)学习笔记
图论的真神——tarjan
其实这个没什么好写的,主要就是缩完点之后树上问题,难点反而是树上的问题。
具体运用——2-sat
强联通缩点,可以类比一个树上的问题?但是有非树边,这里的非树边可以有前向边(就是指向祖先),后向边(指向子孙),横叉边(除了上面的边),很明显后向边对强联通分量是没有影响的,而前项肯定能构造一个强联通,而横叉是根据前向边的情况看是否能构成强联通。可以用 dfn
和 low
来表示这个点的 dfs序
, low
表示它能到达的 dfs序
最小的一个点。如果这个点的 dfn=low 那么它就不能去到更浅的点了,就相当于一个强联通,直接用一个栈模拟即可。
int dfn[N], low[N], dfncnt, st[N], in_stack[N], top;
int id[N], sc; // 结点 i 所在 SCC 的编号
vector<int> scc; // SCC i 所拥有的结点
void tarjan(int u) {
low[u] = dfn[u] = ++dfncnt;//初始化
st[++top] = u, in_stack[u] = 1;
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (in_stack[v]) { //如果在栈里,则继续更新 前向边 和 一部分横叉边
low[u] = min(low[u], dfn[v]);//此处写dfn[v] 或 low[v]都行
}
}
if (dfn[u] == low[u]) {
++sc;
int y;
do{
y=st[top--],in_stack[y]=0;//出栈
id[y]=sc,scc[sc].push_back(y);//标记
}while(y!=u);
}
}
点双联通和边双联通都是一样的。
边双跟强联通分量缩点很像:
void tarjan(int x,int in_edge){
dfn[x]=low[x]=++cnt;
st[++top]=x;
for(int i=head[x];~i;i=nxt[i]){
if(i==(in_edge^1))continue;
int v=to[i];
if(!dfn[v]){
tarjan(v,i);
low[x]=min(low[x],low[y]);
if(low[v]>dfn[x])
bridge[i]=bridge[i^1]=true;
}
else low[x]=min(low[x],dfn[v]);
}
if(dfn[x]==low[x]){
++dcc_cnt;
int y;
do{
y=st[top--],dcc[x]=dcc_cnt;
}while(y!=x);
}
}
点双:
void tarjan(int u,int ID) {
vis[u]=1;
low[u]=dfn[u]=++cnt;
st[++top]=u;
if(rt==u && !e[u].size()) {
g[++sc].push_back(u);
return ;
}
int flag=0;
for(auto tmp:e[u]) {
int v=tmp.first,id=tmp.second;
//if((id^1) ==(ID^1))continue;
if(!dfn[v]) {
tarjan(v,id);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]) {
++flag;
++sc;
int y;
do {
y=st[top--];
id[y]=sc;
h[sc].push_back(y);
} while(y!=v);
id[u]=u;
}
} else low[u]=min(low[u],dfn[v]);
}
}