“tarjan陪伴强联通分量
生成树完成后思路才闪光
欧拉跑过的七桥古塘
让你 心驰神往”—《膜你抄》
tarjan算法是基于对有向图的深度优先搜索的算法,主要用于求解强连通分量,时间复杂度是线性的 O(n+m)其中n为点数,m为边数。tarjan的算法关键在搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量,具体实现这一算法,我们需要使用一些辅助数组,其意义如下所述:
dfn[ ]:时间戳数组,表示这个点在dfs时被搜索到的顺序;
low[ ]:时间戳数组,表示回溯时点被搜索到的顺序;
flag[ ]:布尔型数组,标记某个点是否被访问;
stack[ ]:模拟栈的功能的数组,某些情况可能不需要这一数组;
下面用文字来描述一下算法的具体流程:
1.首先从节点1开始dfs,将遍历到的节点加入栈中,如果发现dfn[u]=low[u],则找到了强连通分量,退栈直到u=v;
2.回溯,发现dfn[v]=low[v],{v}为强连通分量;
3.继续返回节点1搜索,至所有节点都被访问,算法结束
缩点模板
用tarjan求解强连通分量,将强连通分量缩点后重新建图,最后跑一遍spfa求最大值。
#include
using namespace std;
const int MAXM=100010;
const int MAXN=10010;
int head[MAXM],val[MAXM],x[MAXM],y[MAXM],dis[MAXM],f[MAXM];
int dfn[MAXM],low[MAXM],color[MAXM],s[MAXM];
int n,m,tmp,t,ct,top,ans;
bool flag[MAXN];
struct Edge {
int to;
int from;
int next;
} edge[MAXM];
void init() {
memset(head,0,sizeof(head));
memset(flag,false,sizeof(flag));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
tmp=t=top=ans=0;
}
void add(int from,int to) {
tmp++;
edge[tmp].to=to;
edge[tmp].from=from;
edge[tmp].next=head[from];
head[from]=tmp;
}
void tarjan(int now) {
dfn[now]=low[now]=++t;
flag[now]=true;
s[++top]=now;
for(int i=head[now],v=edge[i].to; i>0; i=edge[i].next,v=edge[i].to) {
if(dfn[v]==0) {
tarjan(v);
low[now]=min(low[now],low[v]);
}
else if(flag[v]) {
low[now]=min(low[now],dfn[v]);
}
}
if(dfn[now]==low[now]) {
ct++;
flag[now]=false;
while(s[top+1]!=now){
color[s[top]]=ct;
f[ct]+=val[s[top]];
ans=max(ans,f[ct]);
flag[s[top]]=false;
top--;
}
}
}
void spfa(int x) {
memset(dis,0,sizeof(dis));
memset(flag,0,sizeof(flag));
dis[x]=f[x];
queue <int> q;
flag[x]=true;
q.push(x);
while(!q.empty()) {
int u=q.front();
q.pop();
flag[u]=false;
for(int i=head[u],v=edge[i].to; i>0; i=edge[i].next,v=edge[i].to) {
if(dis[v]if(!flag[v]) {
flag[v]=true;
q.push(v);
}
}
}
}
for(int i=1; i<=ct; i++) {
ans=max(dis[i],ans);
}
}
int main() {
init();
scanf("%d %d",&n,&m);
for(int i=1; i<=n; i++) {
scanf("%d",&val[i]);
}
for(int i=1; i<=m; i++) {
int from,to;
scanf("%d %d",&from,&to);
x[i]=from;
y[i]=to;
add(from,to);
}
for(int i=1; i<=n; i++) {
if(dfn[i]==0) tarjan(i);
}
tmp=0;
memset(head,0,sizeof(head));
memset(edge,0,sizeof(edge));
for(int i=1;i<=m;i++){
if(color[x[i]]!=color[y[i]]) add(color[x[i]],color[y[i]]);
}
for(int i=1;i<=ct;i++){
spfa(i);
}
printf("%d\n",ans);
return 0;
}
求割点的模板
割点就是删去了这个点后图变为不再连通的点
判根节点是否为割点非常简单,对于不是根的节点,如果有 low[v]>=dfn[now] ,则该节点是割点,注意
cnt不要对重复的点多次计数。
#pragma GCC optimize(3)
#include
using namespace std;
const int MAXN=200010;
int ans[MAXN],p[MAXN],dfn[MAXN],low[MAXN],head[MAXN];
bool res[MAXN],flag[MAXN];
int tmp=0,t=0,cnt=0;
int n,m;
struct Edge {
int to;
int next;
} edge[MAXN];
void init() {
for(int i=1;i<=n;i++){
head[i]=0;dfn[i]=0;low[i]=0;p[i]=i;
}
}
void add(int from,int to) {
tmp++;
edge[tmp].next=head[from];
edge[tmp].to=to;
head[from]=tmp;
}
void tarjan(int now) {
int child=0;
dfn[now]=low[now]=++t;
for(int i=head[now]; i>0; i=edge[i].next) {
int v=edge[i].to;
if(!dfn[v]) {
p[v]=p[now];
tarjan(v);
low[now]=min(low[now],low[v]);
if(now!=p[now]&&low[v]>=dfn[now]){
if(res[now]==false) cnt++;
res[now]=true;
}
if(now==p[now]) child++;
}
low[now]=min(low[now],dfn[v]);
}
if(now==p[now]&&child>=2) {
if(res[now]==false) cnt++;
res[now]=true;
}
}
int main() {
scanf("%d %d",&n,&m);
init();
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++){
if(!dfn[i]) tarjan(i);
}
printf("%d\n",cnt);
for(int i=1;i<=n;i++){
if(res[i]==true) printf("%d ",i);
}
}