**~~
**~~
强连通分量:
强连通:有向图中两个结点如果能两两互相到达,就称这两个点强连通。
强连通图:有向图中所有结点都能两两到达,就称强连通图。
强连通分量:非强连通的有向图的极大强连通子图,称为强连通分量(SCC即Strongly Connected Componenet)。一个图中可以有多个。
强连通分量是一个环或者一个点。
模板
const int MAX_N=1e5+5;
const int MAX_E=3e5+5;
struct Edge{ int to,nxt; }e[MAX_E];
int head[MAX_N],tote;
void add_edge(int u,int v){e[++tote].to=v,e[tote].nxt=head[u];head[u]=tote;}
struct Data_Edge{ int u,v; }de[MAX_E];//数据边
int idx,dfn[MAX_N],low[MAX_N];//dfn时间戳 low一个点及其子树的点可追溯到的最早的时间戳
int belong[MAX_N],num[MAX_N],scc;//belong 一个点属于哪个scc,num 一个scc的大小,scc总scc数
int Stack[MAX_N],top;
bool in_stack[MAX_N];
void tarjan(int u)
{
dfn[u]=low[u]=++idx;
Stack[++top]=u,in_stack[u]=true;
reps(u){
if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
else if(in_stack[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
belong[u]=++scc,in_stack[u]=false,num[scc]=1;
while(Stack[top]!=u) belong[Stack[top]]=scc,in_stack[Stack[top--]]=false,num[scc]++;
top--;
}
}
int in[MAX_N],out[MAX_N];
vector<int> g[MAX_N];//缩点后的新图
void condensation_point(int n,int m)//点数 边数
{
repi(i,1,n) in[i]=out[i]=0,g[i].clear();
repi(i,1,n)if(!dfn[i]) tarjan(i);
if(scc==1) return;
repi(i,1,m){
int u=de[i].u,v=de[i].v;
if(belong[u]!=belong[v]) out[belong[u]]++,in[belong[v]]++,g[belong[u]].pb(belong[v]);
}
}
void init(int n)
{
repi(i,1,n) dfn[i]=in_stack[i]=head[i]=0;
tote=idx=scc=top=0;
}
/*
int n,m; si(n),si(m); init(n);
repi(i,1,m){
int u,v; si(u),si(v);
add_edge(u,v),de[i]={u,v};
}
condensation_point(n,m);
*/
2-SAT
连边模型:
记x为选,no(x)/x’为不选
模型一:两者(A,B)不能同时取
那么选择了A就只能选择B’,选择了B就只能选择A’
连边A→B’,B→A’
模型二:两者(A,B)不能同时不取(至少选一个)
那么选择了A’就只能选择B,选择了B’就只能选择A
连边A’→B,B’→A
模型三:两者(A,B)要么都取,要么都不取
那么选择了A,就只能选择B,选择了B就只能选择A,选择了A’就只能选择B’,选择了B’就只能选择A’
连边A→B,B→A,A’→B’,B’→A’
模型四:两者(A,A’)必取A
连边A’→A
建图后跑SCC,若x与no(x)在同一scc则无解,反之有解。
模板
#define no(x) ((x)+n)//选1-n 不选n+1-2*n
struct Edge{int to,nxt;}e[MAX_E];//边数上界根据题目估算
int head[MAX_N],tote;//点数上界*2
void add_edge(int u,int v){e[++tote].to=v,e[tote].nxt=head[u];head[u]=tote;}
int idx,dfn[MAX_N],low[MAX_N];//dfn??? low????????????????????
int belong[MAX_N],num[MAX_N],scc;//belong ???????scc,num ??scc???,scc?scc?
int Stack[MAX_N],top;
bool in_stack[MAX_N];
void tarjan(int u)
{
dfn[u]=low[u]=++idx;
Stack[++top]=u,in_stack[u]=true;
reps(u){
if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
else if(in_stack[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
belong[u]=++scc,in_stack[u]=false,num[scc]=1;
while(Stack[top]!=u) belong[Stack[top]]=scc,in_stack[Stack[top--]]=false,num[scc]++;
top--;
}
}
int ans[MAX_N];
void SAT(int n)
{
repi(i,1,2*n)if(!dfn[i]) tarjan(i);
repi(i,1,n){
int x=belong[i],y=belong[no(i)];
if(x==y){puts("-1");return;}//无解
if(x<y) ans[++ans[0]]=i;//选入 ans 1-ans[0]存解
}
}
void init(int n)
{
repi(i,1,n) dfn[i]=in_stack[i]=head[i]=0;
tote=idx=scc=top=ans[0]=0;
}
/*
init(2*n) SAT(n);
建立强连通分量时,按正向连边顺序,从第一个点,也就是选s开始的点搜 那么对于每一组,先被并入一个scc的肯定时选s开始的,故
直接通过比较每对scc的大小即可做出选择。
*/
(还有个构造解的是重建图,反边染色,比这个麻烦,感觉没啥意义就没管了)
DFS找字典序最小解
#define no(x) (x^1) //反点按具体情况 以下以0-1 2-3...分组为例
struct Edge{int to,nxt;}e[MAX_E<<1];
int head[MAX_N],tote;
void add_edge(int u,int v){e[++tote].to=v,e[tote].nxt=head[u];head[u]=tote;}
bool mark[MAX_N];//mark为true表示这个点选入 例子中点下标i 0-2n-1
int top,Stack[MAX_N];
bool dfs(int u)
{
if(mark[no(u)]) return false;
if(mark[u]) return true;
Stack[++top]=u,mark[u]=true;
reps(u)if(!dfs(v)) return false;
return true;
}
bool check(int n)
{
for(int i=0;i<2*n;i+=2)if(!mark[i]&&!mark[no(i)]){
top=0;
if(!dfs(i)){
while(top) mark[Stack[top--]]=false;
if(!dfs(no(i))) return false;
}
}
return true;
}
void init(int n)
{
repi(i,0,n) head[i]=mark[i]=0;
tote=0;
}
/*
init(2*n),check(n)
true有解
*/
割点与桥
割点与桥(割边)的定义
在无向图中才有割边和割点的定义
割点:无向连通图中,去掉一个顶点及和它相邻的所有边,图中的连通分量数增加,则该顶点称为割点。
桥(割边):无向联通图中,去掉一条边,图中的连通分量数增加,则这条边,称为桥或者割边。
割点与桥(割边)的关系:
(1)有割点不一定有桥,有桥一定存在割点
(2)桥一定是割点依附的边。
模板
/*
求 无向图的割点和桥
*/
struct Edge{ int to,nxt;bool cut; }e[MAX_E<<1];
int head[MAX_N],tote;
void add_edge(int u,int v){e[++tote].to=v,e[tote].nxt=head[u],e[i].cut=false;head[u]=tote;}
int rt,idx,dfn[MAX_N],low[MAX_N];//dfn 时间戳 low该点及该点子树最早可追溯到的节点
bool cut[MAX_N];//标记一个点是不是割点
int add_block[MAX_N],bridge;//add_block删除一个点增加的连通块数,bridge桥数
void tarjan(int u,int in_edge)
{
low[u]=dfn[u]=++idx;
int son=0;
reps(u)if(i!=(in_edge^1)){//不走反向边
if(!dfn[v]){
son++,tarjan(v,i);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u]) bridge++,e[i].cut=e[i^1].cut=true;//一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且dfn(u)
if(u!=rt&&low[v]>=dfn[u]) cut[u]=true,add_block[u]++;//一个顶点u是割点,当且仅当满足(1)u为树根,且u有多于一个子树。
//或(2) u不为树根,且满足存在(u,v)为树枝边,使得dfn(u)<=low(v)
}
else low[u]=min(low[u],dfn[v]);
}
if(u==rt&&son>1) cut[u]=true,add_block[u]=son-1;
}
void init(int n)
{
repi(i,1,n) head[i]=dfn[i]=add_block[i]=cut[i]=0;
tote=1,idx=bridge=0;
}
/*
注意加边的时候原边与反边一起加
repi(i,1,n)if(!dfn[i]) rt=i,tarjan(i,1);
*/
双连通分量
双连通分量:有点双连通分量和边双连通分量两种。若一个无向图中的去掉任意一个节点(一条边)都不会改变此图的连通性,即不存在割点(桥),则称作点(边)双连通图。一个无向图中的每一个极大点(边)双连通子图称作此无向图的点(边)双连通分量。
点双连通和边双连通
点双连通:删掉一个点之后,图仍联通。任意两点间至少存在两条“点不重复”的路径。在一个无向图中,点双连通的极大子图称为点双连通分量(简称双连通分量,Biconnected Component,BCC)
建立一个栈,存储当前 双连通分支,在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到某时满 足 DFS(u)<=Low(v),说明 u 是一个割点,同时把边从栈顶一个个取出,直到遇到了边(u,v),取出的这些边与 其关联的点,组成一个点双连通分支。割点可以属于多个点双连通分支,其余点和每条边只属于且属于一 个点双连通分支。
边双连通:删掉一条边之后,图仍联通。任意两点间至少存在两条“边不重复”的路径。总而言之就是一个圈,正着走反着走都可以相互到达,至少只有一个点。
性质
1.若BCC间有公共点,则公共点为原图的割点
2.无向连通图中割点一定属于至少两个BCC,非割点只属于一个BCC
3.在一张连通的无向图中,对于两个点u和v,如果无论删去哪条边(只能删去一条)都不能使它们不连通,我们就说u和v边双连通 。
4.在一张连通的无向图中,对于两个点u和v,如果无论删去哪个点(只能删去一个,且不能删 和 自己)都不能使它们不连通,我们就说u和v点双连通 。
5.边双连通具有传递性,即,若x,y边双连通, y,z边双连通,则x,z边双连通。
6.点双连通 不 具有传递性,反例如下图, A,B点双连通, B,C点双连通,而 A,C不点双连通。
一个有桥的连通图,如何把它通过加边变成边双连通图?方法为首先求出所有的桥,然后删除这些桥边, 剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这 个图一定是一棵树,边连通度为 1。 统计出树中度为 1 的节点的个数,即为叶节点的个数,记为 leaf。则至少在树上添加(leaf+1)/2 条边,就能 使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两 个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一 定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2 次, 把所有点收缩到了一起。
模板
点双连通分量
struct Edge{ int to,nxt; }e[MAX_E<<1];
int head[MAX_N],tote;
void add_edge(int u,int v){e[++tote].to=v,e[tote].nxt=head[u];head[u]=tote;}
int rt,idx,dfn[MAX_N],low[MAX_N];//dfn 时间戳 low该点及该点子树最早可追溯到的节点
int Stack[MAX_N],top;
int belong[MAX_N],block;//belong 每个点属于哪个bcc,block:bcc数
bool cut[MAX_N];//标记一个点是不是割点
void tarjan(int u,int in_edge)
{
low[u]=dfn[u]=++idx;
Stack[++top]=u;
int son=0;
reps(u)if(i!=(in_edge^1)){
if(!dfn[v]){
son++,tarjan(v,i);
low[u]=min(low[u],low[v]);
if(dfn[u]<=low[v]){
cut[u]=true,block++;
int vn; tmp[0]=0;
do{
vn=Stack[top--],belong[vn]=block;
}while(vn!=v);//此处不用while写 因为存在u连接一个割点的时候,那个割点不会被从栈中弹出,导致栈中v下面实际不是u
}
}
else if(low[u]>dfn[v]) low[u]=dfn[v];
}
if(u==rt&&son<2) cut[u]=false;
}
void init(int n)
{
repi(i,1,n) head[i]=dfn[i]=belong[i]=cut[i]=0;
idx=top=block=0,tote=1;
}
/*
注意加边的时候原边与反边一起加
repi(i,1,n)if(!dfn[i]) rt=i,tarjan(i,1);
*/
边双连通分量
struct Edge{ int to,nxt; bool cut;}e[MAX_E<<1];
int head[MAX_N],tote;
void add_edge(int u,int v){e[++tote].to=v,e[tote].nxt=head[u],e[tote].cut=false;head[u]=tote; }
vector<int> g[MAX_N];
int idx,dfn[MAX_N],low[MAX_N];//dfn 时间戳 low该点及该点子树最早可追溯到的节点
int belong[MAX_N],dcc;//belong 每个点属于哪个dcc,dcc:dcc数
int bridge;
void tarjan(int u,int in_edge)
{
low[u]=dfn[u]=++idx;
reps(u)if(i!=(in_edge^1)){
if(!dfn[v]){
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u]) bridge++,e[i].cut=e[i^1].cut=true;
}
else low[u]=min(low[u],dfn[v]);
}
}
void dfs_dcc(int u)
{
belong[u]=dcc;
reps(u)if(!e[i].cut&&!belong[v]) dfs_dcc(v);
}
void init(int n)
{
repi(i,1,n) head[i]=dfn[i]=belong[i]=0,g[i].clear();
idx=bridge=dcc=0,tote=1;
}
/*
repi(i,1,n)if(!dfn[i]) tarjan(i,1);
repi(i,1,n)if(!belong[i]) dcc++,dfs_dcc(i);
repi(u,1,n)reps(u)if(belong[u]!=belong[v]) g[belong[u]].pb(belong[v]);
//g为缩点后的图 最好换成链式前向星
*/