My Title

Tarjan的学习

/*推导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\n",sum);
    REP(i,1,n)
        if(vis[i]) printf("%d ",i);
    
    return 0;
}
luogu3388

缩点模板:

#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;
}
luogu3387

 

无向图的双连通分量:一张无向连通图不存在割点。

分为 点双连通分量(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);
}
View Code

简单环就是双连通分量,所以双连通缩点后就是无向无环连通图。

 

有向强连通分量(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);
View Code

 

【最近公共祖先】

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);
倍增+LCA

 

posted @ 2018-03-18 15:19  EvfX  阅读(156)  评论(0编辑  收藏  举报