图论 —— 图的连通性 —— Tarjan 缩点
缩点常应用于给一个有向图,求在图中最少要加多少条边能使得该图变成一个强连通图
首先求出该图的各个强连通分量,然后把每个强连通分量看出一个点(即缩点),最后得到了一个有向无环图(DAG)
对于一个DAG,需要添加 max(a,b) 条边才能使其强连通
其中 a 为 DAG 中出度为 0 的点总数,b 为 DAG 中入度为 0 的点总数
int n,m;
vector<int> G[N];
stack<int> S;
int dfn[N],low[N];
bool vis[N];//标记数组
int sccno[N];//记录结点i属于哪个强连通分量
bool in[N],out[N];//记录入度、出度是否为0
int block_cnt;//时间戳
int sig;//记录强连通分量个数
void Tarjan(int x){
vis[x]=true;
dfn[x]=low[x]=++block_cnt;//每找到一个新点,纪录当前节点的时间戳
S.push(x);//当前结点入栈
for(int i=0;i<G[x].size();i++){//遍历整个栈
int y=G[x][i];//当前结点的下一结点
if(vis[y]==false){//若未被访问过
Tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(!sccno[y])//若已被访问过,且不属于任何一个连通分量
low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x]){//满足强连通分量要求
sig++;//记录强连通分量个数
while(true){//记录元素属于第几个强连通分量
int temp=S.top();
S.pop();
sccno[temp]=sig;
if(temp==x)
break;
}
}
}
void shrink(){//缩点
memset(in,false,sizeof(in));
memset(out,false,sizeof(out));
for(int i=1;i<=sig;i++){//对于所有的强连通分量,将其入度、出度均视为1
in[i]=true;
out[i]=true;
}
for(int x=0;x<n;x++){//枚举n个点
for(int i=0;i<G[x].size();i++){//对第x个点的每个后继节点
int y=G[x][i];
if(sccno[x]!=sccno[y]){//统计每个点出度、入度是否为0
out[sccno[x]]=false;
in[sccno[y]]=false;
}
}
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
G[i].clear();
while(m--){
int x,y;
scanf("%d%d",&x,&y);
x--;
y--;
G[x].push_back(y);
}
sig=0;
block_cnt=0;
memset(vis,false,sizeof(vis));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(sccno,0,sizeof(sccno));
//Tarjan求强连通分量
for(int i=0;i<n;i++)
if(vis[i]==false)
Tarjan(i);
shrink();//缩点
int a=0,b=0;
for(int i=1;i<=sig;i++){//统计入度、出度为0的点的个数
if(in[i])
a++;
if(out[i])
b++;
}
int res=max(a,b);
if(sig==1)//强连通分量为1时
res=0;
printf("%d\n",res);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!