Tarjan 缩点

Tarjan 缩点

用于求解强连通分量相关问题
在DAG图中
对于一个点x有low[ ] (用于记录该点所在强连通分量的入口位置),
dfn[ ](用于记dfs时重新编号遍历之后的序号),
初始值low=dfn
另有point[ ] 用于记录一个强连通分量(新点)
pw[ ] 记录各新点(总)权值

开一个栈stack
每当遍历到一个点时将其加入栈
并扫描所有当前点连接的点
如果!dfn[ ] 即没有被访问过
那么进入该点并在返回时更新当前点low值(后向边√)
否则检查该点是否在stack内

如果不在stack内但是dfn不是0
说明这个点已经被访问过并且属于另外一个强连通分量(横叉边×)
如果在的话更新当前点low值

当进行完上述操作后 如果这个时候这个点的low值还没有被更新
即 low == dfn
那么说明这个点就是他所在强连通分量的一个入口点
直接退栈 直到把这个点退掉
然后这次从栈里退出来这些点就和当前点属于一个强连通分量
退栈时顺便就加到point里面然后把这些点的权值加到pw里面了

对于新的点point需要重新建图
自己想的一种做法是:
退栈的时候顺便遍历一遍退掉的这些点连接的其他point里面的点
然鹅看网上代码大多都是dfs完之后
再访问用结构体(或者vector)记录的原图边
每条边都有(u,v)两个点
如果point[u]!=point[v] 那么就add(point[a],point[b])到新图中
所以还是从众了。。

luogu P3378 缩点

const int maxn=1e5+10;
struct rec {
    int f,t,next;
}e[maxn],ee[maxn];
int now[maxn],w[maxn],p,pp,noww[maxn];
void add(int x,int y)
{
    p++;
    e[p].f=x;
    e[p].t=y;
    e[p].next=now[x];
    now[x]=p;
} 
void addd(int x,int y)
{
    pp++;
    ee[pp].f=x;
    ee[pp].t=y;
    ee[pp].next=noww[x];
    noww[x]=pp;
}
 int dfn[maxn],low[maxn],stack[maxn],top,cnt,point[maxn],pcnt,pw[maxn],dp[maxn];
bool instack[maxn];
void tarjan(int x)
{
    dfn[x]=low[x]=++cnt;
    instack[x]=1;
    stack[++top]=x;
    int ed=now[x];
    while(ed)
    {
        int v=e[ed].t;
        if(!dfn[v])
        {
            tarjan(v);
            low[x]=min(low[x],low[v]);// 打的时候这里写成low[v]=...了
        }
        else if(instack[v])low[x]=min(dfn[v],low[x]); 
        ed=e[ed].next;
    }
    if(dfn[x]==low[x])
    {
        int t=top;
        int y=stack[top];
        pcnt++;
        {
            y=stack[top--];
            pw[pcnt]+=w[y];
            //cout<<pw[pcnt];
            instack[y]=0;
            point[y]=pcnt;
        } while(x!=y);//这里要用do while
    /*  while(x!=y)
        {
            y=stack[t--];
            int ed=now[y];
            int v=e[ed].t;
            while(ed)
            {
                if(point[v]!=pcnt) addd(pcnt,point[v]);
                ed=ee[ed].next;
            }
        }*/     //另一种加边方法  
    }
} 
int dfs(int x)
{
    if(dp[x])return dp[x];
    int ed=noww[x];
    int ans=0;
    while(ed)
    {
        int v=ee[ed].t;
        ans=max(dfs(v),ans);
        ed=ee[ed].next;
    }
    return dp[x]=ans+pw[x];
}
int main()
{
    //ios::sync_with_stdio(false);
    int n=read(),m=read();
    for(int i=1;i<=n;i++)
    {
        w[i]=read();
    } 
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        add(x,y); 
    } 
    for(int i=1;i<=n;i++)
    {
        if(!dfn[i])tarjan(i); // tarjan缩点  把全部点遍历防止疏漏
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    { 
        int ed=now[i];
        while(ed)
        {
            if(point[i]!=point[e[ed].t])
            {
                addd(point[i],point[e[ed].t]);   //注意这里是加的point[ ] 
            }
            ed=e[ed].next;
        }
    }
    for(int i=1;i<=pcnt;i++)
    {
        if(!dp[i]){
        ans=max(dfs(i),ans); 
        }     // DAG图上的dp
    } 
    cout<<ans;
    return 0;
 }

一些小细节

  • 弹栈的时候一定要用do while 确保把low == dfn 的点也弹出

  • 可以通过加add函数的参数实现两种加边 不用再写两个函数

  • 一定把点都tarjan一遍防止疏漏

  • 细心细心再细心

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值