求强连通分量
强连通的定义是:有向图 G 强连通是指,G 中任意两个结点连通。
强连通分量(Strongly Connected Components,SCC)的定义是:极大的强连通子图。
题目链接:https://www.luogu.com.cn/problem/P2863
题解:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m;
int cnt;//记录强联通分量的个数
int visitnum;//遍历的步数
int dfn[100010];//记录元素第一次被访问的步数
int low[100010];//包含i的强联通分量最早被访问的步数
int num[100010];//记录强联通分量里的点的个数
int belong[100010];//i从属的强联通分量的序号
int top;//栈中元素的个数
int stack[100010];//手打栈
int instack[100010];//判断元素是否在栈中
int head[100010];
struct node{
int to,next;
}edge[100010];//链式前向星存边
int read()//读入优化
{
int x=0,w=1;char ch=getchar();
while(ch>'9'||ch<'0') {if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*w;
}
void tarjan(int u)
{
int v;
visitnum++;
dfn[u]=low[u]=visitnum;
stack[++top]=u;//入栈
instack[u]=1;//入栈
for(int i=head[u];i;i=edge[i].next)
{
v=edge[i].to;
if(!dfn[v])//还没被访问过
{
tarjan(v);
low[u]=min(low[u],low[v]);//判断u是否为v的子节点
}
else if(instack[v])
{
low[u]=min(low[u],dfn[v]);
//其实这里的dfn[v]也能换成low[v] 但最好写dfn
}
}
if(dfn[u]==low[u])//u为强联通分量的根
{
cnt++;
do//退栈
{
num[cnt]++;
v=stack[top--];
belong[v]=cnt;
instack[v]=0;
}while(u!=v);
}
}
int main()
{
int ans=0;
int p,q;
n=read();m=read();
for(int i=1;i<=m;i++)
{
p=read();q=read();
edge[i].to=q;
edge[i].next=head[p];
head[p]=i;
}
for(int i=1;i<=n;i++)
{
if(!dfn[i])//i没被访问过了
{
tarjan(i);
}
}
for(int i=1;i<=cnt;i++)
{
if(num[i]>1)
ans++;
}
printf("%d",ans);
}
求双连通分量
①点连通分量(BCC)
在一个无向图中,若任意两点间至少存在两条“点不重复”的路径,则说这个图是点双连通的(简称双连通,biconnected)
在一个无向图中,点双连通的极大子图称为点双连通分量(简称双连通分量,Biconnected Component,BCC)
模板
#include<cstdio>
#include<cctype>
#include<vector>
using namespace std;
struct edge
{
int to,pre;
}edges[1000001];
int head[1000001],dfn[1000001],dfs_clock,tot;
int num;//BCC数量
int stack[1000001],top;//栈
vector<int>bcc[1000001];
int tarjan(int u,int fa)
{
int lowu=dfn[u]=++dfs_clock;
for(int i=head[u];i;i=edges[i].pre)
if(!dfn[edges[i].to])
{
stack[++top]=edges[i].to;//搜索到的点入栈
int lowv=tarjan(edges[i].to,u);
lowu=min(lowu,lowv);
if(lowv>=dfn[u])//是割点或根
{
num++;
while(stack[top]!=edges[i].to)//将点出栈直到目标点
bcc[num].push_back(stack[top--]);
bcc[num].push_back(stack[top--]);//目标点出栈
bcc[num].push_back(u);//不要忘了将当前点存入bcc
}
}
else if(edges[i].to!=fa)
lowu=min(lowu,dfn[edges[i].to]);
return lowu;
}
void add(int x,int y)//邻接表存边
{
edges[++tot].to=y;
edges[tot].pre=head[x];
head[x]=tot;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
for(int i=1;i<=n;i++)//遍历n个点tarjan
if(!dfn[i])
{
stack[top=1]=i;
tarjan(i,i);
}
for(int i=1;i<=num;i++)
{
printf("BCC#%d: ",i);
for(int j=0;j<bcc[i].size();j++)
printf("%d ",bcc[i][j]);
printf("\n");
}
return 0;
}
②边强连通分量
与求割边的过程相同,详细代码见下面的求割边代码。
求割点
例题:https://www.luogu.com.cn/problem/P3388
题解
#include<bits/stdc++.h>
using namespace std;
struct edge{
int nxt,mark;
}pre[200010];
int n,m,idx,cnt,tot;
int head[100010],DFN[100010],LOW[100010];
bool cut[100010];
void add (int x,int y)
{
pre[++cnt].nxt=y;
pre[cnt].mark=head[x];
head[x]=cnt;
}
void tarjan (int u,int fa)
{
DFN[u]=LOW[u]=++idx;
int child=0;
for (int i=head[u];i!=0;i=pre[i].mark)
{
int nx=pre[i].nxt;
if (!DFN[nx])
{
tarjan (nx,fa);
LOW[u]=min (LOW[u],LOW[nx]);
if (LOW[nx]>=DFN[u]&&u!=fa)
cut[u]=1;
if(u==fa)
child++;
}
LOW[u]=min (LOW[u],DFN[nx]);
}
if (child>=2&&u==fa)
cut[u]=1;
}
int main()
{
memset (DFN,0,sizeof (DFN));
memset (head,0,sizeof (head));
scanf ("%d%d",&n,&m);
for (int i=1;i<=m;i++)
{
int a,b;
scanf ("%d%d",&a,&b);
add (a,b);
add (b,a);
}
for (int i=1;i<=n;i++)
if (DFN[i]==0)
tarjan (i,i);
for (int i=1;i<=n;i++)
if (cut[i])
tot++;
printf ("%d\n",tot);
for (int i=1;i<=n;i++)
if (cut[i])
printf ("%d ",i);
return 0;
}
求割边
例题:https://www.luogu.com.cn/problem/P1656
题解:
#include <bits/stdc++.h>
using namespace std;
int maps[151][151];//邻接矩阵
struct Edge {
int x,y;
} E[5001];//这是存答案的,用邻接表存,应该不用解释
int dfn[151],low[151],n,m,id,cnt,f[151];/*这些数组的含义:
dfn:
{
下标:点编号
内存的值:深度优先搜索时第几个遍历
}
low:
{
下标:点编号
内存的值:这个点能通过它的子孙到达的dfn值最小的点的dfn
}
f:
{
下标:点标号
内存的值:它遍历的上一个点
}
变量的含义:
n:结点个数
m:边个数
id:用于dfn标记
cnt:用于邻接表存图
*/
bool cmp(struct Edge a,struct Edge b) {
if(a.x==b.x)return a.y<b.y;
return a.x<b.x;
}//因题目要求,边要排序,要做这道题的人应该都知道cmp
void addEdge(int x,int y) {
E[++cnt].x=x;
E[cnt].y=y;
}//addedge函数,存入邻接表
void tarjan(int x) {
int c=0,y;
dfn[x]=low[x]=++id;
for(register int i=1; i<=n; i++) {
if(!maps[x][i])continue;//首先要有边
y=i;//处理对象
if(dfn[y]&&y!=f[x])low[x]=min(low[x],dfn[y]);//如果是它爸爸,割边就没有用了,好好理解
if(!dfn[y]) {//如果找到祖先还有什么用呢
f[y]=x;//不是祖先就认爸爸
tarjan(y);//dfs过程
low[x]=min(low[x],low[y]);//回溯时带着爸爸更新low
if(low[y]>dfn[x])addEdge(x,y);//是割边,就加入吧
}
}
}//tarjan部分,证明在下面
int main() {
int x,y;
cin>>n>>m;
for(register int i=1; i<=m; i++) {
cin>>x>>y;
maps[x][y]=maps[y][x]=1;//存边
}
for(register int i=1; i<=n; i++) {
if(!dfn[i])tarjan(i);//tarjan
}
sort(E+1,E+cnt+1,cmp);//sort大法好
for(register int i=1; i<=cnt; i++) {
cout<<min(E[i].x,E[i].y)<<' '<<max(E[i].x,E[i].y)<<endl;//输出
}
return 0;//程序结束了
}
求缩点
缩点:顾名思义,就是将一些点缩成一个点的算法。
题目链接:https://www.luogu.com.cn/problem/P3387
题解:
#include<bits/stdc++.h>
using namespace std;
const int maxn=10000+15;
int n,m,sum,tim,top,s;
int p[maxn],head[maxn],sd[maxn],dfn[maxn],low[maxn];//DFN(u)为节点u搜索被搜索到时的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号
int stac[maxn],vis[maxn];//栈只为了表示此时是否有父子关系
int h[maxn],in[maxn],dist[maxn];
struct EDGE
{
int to;int next;int from;
}edge[maxn*10],ed[maxn*10];
void add(int x,int y)
{
edge[++sum].next=head[x];
edge[sum].from=x;
edge[sum].to=y;
head[x]=sum;
}
void tarjan(int x)
{
low[x]=dfn[x]=++tim;
stac[++top]=x;vis[x]=1;
for (int i=head[x];i;i=edge[i].next)
{
int v=edge[i].to;
if (!dfn[v]) {
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if (vis[v])
{
low[x]=min(low[x],low[v]);
}
}
if (dfn[x]==low[x])
{
int y;
while (y=stac[top--])
{
sd[y]=x;
vis[y]=0;
if (x==y) break;
p[x]+=p[y];
}
}
}
int topo()
{
queue <int> q;
int tot=0;
for (int i=1;i<=n;i++)
if (sd[i]==i&&!in[i])
{
q.push(i);
dist[i]=p[i];
}
while (!q.empty())
{
int k=q.front();q.pop();
for (int i=h[k];i;i=ed[i].next)
{
int v=ed[i].to;
dist[v]=max(dist[v],dist[k]+p[v]);
in[v]--;
if (in[v]==0) q.push(v);
}
}
int ans=0;
for (int i=1;i<=n;i++)
ans=max(ans,dist[i]);
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
scanf("%d",&p[i]);
for (int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
}
for (int i=1;i<=n;i++)
if (!dfn[i]) tarjan(i);
for (int i=1;i<=m;i++)
{
int x=sd[edge[i].from],y=sd[edge[i].to];
if (x!=y)
{
ed[++s].next=h[x];
ed[s].to=y;
ed[s].from=x;
h[x]=s;
in[y]++;
}
}
printf("%d",topo());
return 0;
}