/*推导https://blog.csdn.net/qq_34374664/article/details/77488976
dfn[]为这个点搜索的次序编号(时间戳)
low[]为每个点在这颗树中最小的子树的根
每次找到一个新点,这个点low[]=dfn[]
【强连通分量】
割点模板:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> #include<queue> #include<set> using namespace std; #define REG register #define REP(i,x,y) for(register int i=x;i<=y;i++) #define UP(i,x,y) for(register int i=x;i>=y;i--) #define IN inline #define inf 0x3f3f3f3f const int maxn=100005; int h[maxn],dfn[maxn],low[maxn],fa[maxn],n,m,tot=0,num=0,sum=0,root; bool vis[maxn]; struct node{ int u,v; }e[maxn<<1]; IN void add(int u,int v){ e[++tot].u=h[u],e[tot].v=v,h[u]=tot; } IN void TARJAN(int x){ dfn[x]=low[x]=++num; int flag=0; for(REG int i=h[x];i;i=e[i].u){ int v=e[i].v; if(!dfn[v]){ TARJAN(v); low[x]=min(low[x],low[v]); if(low[v]>=dfn[x]){ flag++; if(x!=root || flag>1) vis[x]=true; } } else low[x]=min(low[x],dfn[v]); } } int main(){ scanf("%d %d",&n,&m); REP(i,1,m){ int x,y; scanf("%d %d",&x,&y); if(x==y) continue; add(x,y);add(y,x); } REP(i,1,n) if(!dfn[i]) root=i,TARJAN(i); REP(i,1,n) if(vis[i]) sum++; printf("%d ",sum); REP(i,1,n) if(vis[i]) printf("%d ",i); return 0; }
缩点模板:
#include<cstdio> #include<iostream> #include<algorithm> #include<cstring> #include<cmath> #include<set> #include<vector> #include<queue> #include<stack> #include<cstdlib> using namespace std; #define REG register #define IN inline #define REP(i,x,y) for(REG int i=x;i<=y;i++) #define inf 0x3f3f3f3f const int maxn=100005; int n,m,u,v,top,time=0,ctot,ans,tot=0; bool vis[maxn]; int sta[maxn]; int clor[maxn];//染色(联通分量编号) int num[maxn];//联通分量点权和 int dfn[maxn],low[maxn],x[maxn],y[maxn],a[maxn]; int dis[maxn],first[maxn]; struct n{ int to,nt; }node[maxn]; IN void add(int u,int v){ node[++tot].to=v,node[tot].nt=first[u],first[u]=tot; } IN void tarjan(int x){ dfn[x]=low[x]=++time; sta[++top]=x; vis[x]=1; for(int i=first[x];i;i=node[i].nt){ int j=node[i].to; if(vis[j])low[x]=min(low[x],dfn[j]); else if(!dfn[j])tarjan(j),low[x]=min(low[j],low[x]); } if(low[x]==dfn[x]){ ++ctot; vis[x]=0; while(sta[top+1]!=x){ clor[sta[top]]=ctot;//染色 num[ctot]+=a[sta[top]];//同一连通分量点权和 vis[sta[top]]=0; top--; } } } IN void spfa(int x){ memset(dis,0,sizeof(dis)); memset(vis,0,sizeof(vis)); queue<int> q; dis[x]=num[x]; vis[x]=1; q.push(x); while(!q.empty()){ int j=q.front(); vis[j]=0; q.pop(); for(int i=first[j];i;i=node[i].nt){ int t=node[i].to; if(dis[t]<dis[j]+num[t]){ dis[t]=dis[j]+num[t]; if(!vis[t]){ vis[t]=1; q.push(t); } } } } REP(i,1,ctot) ans=max(ans,dis[i]); } int main(){ scanf("%d %d",&n,&m); REP(i,1,n)scanf("%d",&a[i]); REP(i,1,m){ scanf("%d %d",&u,&v); add(u,v); //x[i]=u;y[i]=v; } REP(i,1,n) if(!dfn[i]) tarjan(i); memset(first,0,sizeof(first)); memset(node,0,sizeof(node)); REP(i,1,m) if(clor[u]!=clor[v]) add(clor[u],clor[v]); REP(i,1,ctot) spfa(i); printf("%d",ans); return 0; }
无向图的双连通分量:一张无向连通图不存在割点。
分为 点双连通分量(v-DCC) 和 边双连通分量(e-DCC)。
图中任意两点都同时包含在至少一个简单环中。
e-DCC模板:
void tarjan(int x,int fa){ dfn[x]=low[x]=++dfsnum; for(int i=first[x];i;i=e[i].from)if((i^1)!=fa){ if(!dfn[e[i].v]){ tarjan(e[i].v,i); low[x]=min(low[x],low[e[i].v]); if(low[e[i].v]>dfn[x])iscut[i]=iscut[i^1]=1; }else low[x]=min(low[x],dfn[e[i].v]); } } void dfs(int x){ col[x]=colnum; for(int i=first[x];i;i=e[i].from)if(!col[e[i].v]&&!iscut[i])dfs(e[i].v); }
简单环就是双连通分量,所以双连通缩点后就是无向无环连通图。
有向强连通分量(SCC):
将图变为DAG
void tarjan(int x) { dfn[x]=low[x]=++mark; s[++top]=x;lack[x]=top; for(int i=first[x];i;i=e[i].from) { int y=e[i].v; if(!dfn[y]) { tarjan(y); low[x]=min(low[x],low[y]); } else if(!col[y])low[x]=min(low[x],dfn[y]); } if(dfn[x]==low[x]) { color++; for(int i=lack[x];i<=top;i++)col[s[i]]=color; num[color]=top-lack[x]+1; top=lack[x]-1; } } for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
【最近公共祖先】
void add(int u,int v){ e[k].v=v; e[k].next=head[u]; head[u]=k++; } void dfs(int u,int fa){ d[u]=d[fa]+1; p[u][0]=fa; for(int i=1;(1<<i)<=d[u];i++) p[u][i]=p[p[u][i-1]][i-1]; for(int i=head[u];i!=-1;i=e[i].next){ int v=e[i].v; if(v!=fa) dfs(v,u); } } int lca(int a,int b){ if(d[a]>d[b]) swap(a,b); for(int i=20;i>=0;i--) if(d[a]<=d[b]-(1<<i)) b=p[b][i]; if(a==b) return a; for(int i=20;i>=0;i--){ if(p[a][i]==p[b][i]) continue; else a=p[a][i],b=p[b][i]; } return p[a][0]; } dfs(s,0);