Tarjan求缩点——luogu P3387 【模板】缩点

蒟蒻的垂死挣扎

大佬都在搞这个,我就跟风写一波。

这题题目里明说了是Tarjan缩点+DAG DP,肯定就要这么打嘛,毕竟板子题,怕自己冲学科完回来忘了Tarjan,记录下。

首先是Tarjan的写法,这是个大难点,理解之后倒是挺简单的,具体来说就是用dfs的时间戳来判断是否为强连通分量,标准变量名就是dfn数组记录时间戳,low数组记录 可以到的 最接近根的 点 的时间戳,因为是最接近根,所以每次可以到达或回溯时都要求一次min,这样就防止了环套环的情况,这个倒好理解。然后再是对栈的运用,众所周知,dfs就是有栈的模拟,这里记一个数组str来记录栈中的元素、即还未回溯的点,并用一个bool数组判断是否在栈中。每次找到dfn不为零并仍在栈中的,就用于更新答案——这里防止了一个情况,即排除不能成环但已遍历过的点。遍历过一个点的边后,我们用 if(dfn[k]==low[k]) 判断它为该强连通分量的根,若是,则弹栈,在此题中,将同时弹出(同个强连通块)的所有点的权值压到一个新点中,即可得到点的压缩。

然后我们建一个新图,具体做法为枚举每条边,若边的起点终点不是同一个强连通块内的,则按方向将这两个强连通块连一条有向边,对于其他应用此知识点的题,一般都是采取如上判断方法,如枚举完后判断出度等。

最后用拓扑序做一遍DP,这个就不说了。

上代码!

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#define RG register
using namespace std;

inline long long rread()
{
    RG long long x=0,o=1;
    char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') o=-1,ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*o;
}

int aa[100001],bb[100001],n,m,top,first[10001],p[10001],dfn[10001],low[10001],str[10001],be[10001],cnt,num,sum,si[10001],In[10001],fa[10001],q[10001],aans[10001];
bool b[10001];

struct mona{
    int nxt,en;
}s[100001];

inline void Insert(int x,int y)
{
    s[++top]=(mona) {first[x],y};
    first[x]=top;
}

inline void init()
{
    n=rread(),m=rread();
    for(RG int i=1;i<=n;i++) p[i]=rread();
    for(RG int i=1;i<=m;i++)
    {
        int x=aa[i]=rread(),y=bb[i]=rread();
        Insert(x,y);
    }
}

void Dfs(int k)
{
    low[k]=dfn[k]=(++cnt);
    b[k]=1;
    str[++num]=k;
    
    for(RG int i=first[k];i;i=s[i].nxt)
    {
        int en=s[i].en;
        if(!dfn[en])
        {
            Dfs(en);
            low[k]=min(low[k],low[en]);
        }
        else if(b[en]) low[k]=min(low[k],dfn[en]);
    }
    
    if(low[k]==dfn[k])
    {
        int j;
        sum++;
        do
        {
            j=str[num--];
            be[j]=sum;
            b[j]=0;
            si[sum]+=p[j];
        }while(k!=j);
    }
}

inline void Build()
{
    memset(first,0,sizeof(first));
    memset(s,0,sizeof(s));
    top=0;
    
    for(RG int i=1;i<=m;i++)
        if(be[aa[i]]!=be[bb[i]]) In[be[bb[i]]]++,Insert(be[aa[i]],be[bb[i]]);
}

inline void Top_sort()
{
    int top=0,tail=0,mmax=0;
    for(RG int i=1;i<=sum;i++)
        if(!In[i]) q[++tail]=i;
    
    while(top<tail)
    {
        top++;
        aans[q[top]]+=si[q[top]];
        mmax=max(mmax,aans[q[top]]);
        for(RG int i=first[q[top]];i;i=s[i].nxt)
        {
            In[s[i].en]--;
            aans[s[i].en]=max(aans[s[i].en],aans[q[top]]);
            if(!In[s[i].en]) q[++tail]=s[i].en;
        }
    }
    
    cout<<mmax;
}

int main()
{
//	freopen("0.in","r",stdin);
    init();
    for(RG int i=1;i<=n;i++)
    if(!dfn[i])  Dfs(i);
    Build();
    Top_sort();
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值