缩点

· · 题解

学了tarjan算法,今天我就简单谈谈我对其的理解吧;

废话不多说先上一道题(洛谷P3387),又是一道模板题,对吧(会的大佬勿喷); 题目背景

缩点+DP

题目描述

给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。

允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。

这道题怎么做(我放在这里当然是用tarjan了),可能有其他的方法但今天讲tarjan算法那我们就主讲它吧;

tarjan算法的主要思想就是将互相间可以到达的点(及环)缩成一个点,然后就会构成一个有向无环图,对于有向无环图(因为其具有很多优良的性质)便可以进行DP,或者其它一些操作;

下面我就来讲一下怎么将一些可以互相到达的点缩成一个点; 其中用黄圈 圈起来的点便可以缩成一个点(及把它们强联通);

那怎么求点之间是否强联通呢?我们可以先随便找一个点对它进行dsf,我们用一个数组dfn来记录它被dfs时的顺序;

我们从1号点开始dfs图上的序号便是dfn,我们再用一个low数组来记录本号节点最早可以到达的点的dfn,我们可以发现当一个点的low小于它自己的low时,它便可以到达其他点,这是就能形成环。如上图,我们dfs到了7,发现还可以到5所以四的low便被更新为5,这个很容易看出来是强连通的,但左侧的并不是前联通的,我们就应该开一个栈来记录。那么便会出现下面几种情况:

子节点(v)已访问过,并且在栈里非常显然,该节点(u)可以回到他的某一祖宗(v)处,从祖宗节点(v)到该节点(u)中间的所有节点可以连成环 将该节点(u)的访问时间标记为 重新标记为该环最小的时间(缩点)子节点(v)已访问过。

不在栈里无法连成环,一个节点的两棵子树间的单向边子节点(v)未访问过继续 DFS 遍历该子节点(v) 遍历完该子节点(v)的所有子节点后,尝试缩点将该节点(u)的时间标记 重新标记为该节点(u)与子节点(v)时间标记的最小值(如果需要修改就证明它的 某个后代节点和它的祖宗节点有连接 )

话不多说,上代码

void tarjan(int u)
{
    dfn[u]=++dfn[0];
    low[u]=dfn[u];//初始化
    st[++st[0]]=u;//st是栈
    inst[u]=1;//表示在栈里
    for(int i=0;i<q[u].size();++i)
    {
        int v=q[u][i];
        if(!dfn[v])//没被遍历过
            tarjan(v);
        if(inst[v])
            low[u]=min(low[v],low[u]);//更新low
    }
    if(dfn[u]==low[u])//缩点
    {
        liantong[0]++;
        while(st[st[0]]!=u)
        {
            liantong[st[st[0]]]=liantong[0];
            sum[liantong[0]]+=va[st[st[0]]];
            inst[st[st[0]]]=0;
            st[0]--;
        }
        liantong[st[st[0]]]=liantong[0];
            sum[liantong[0]]+=va[st[st[0]]];
            inst[st[st[0]]]=0;
            st[0]--;
    }
}

把这个学会了,其它的就是小意思了,我剩下用的spfa解的这道题,那现在就上全代码吧

#include<bits/stdc++.h>
using namespace std;
vector<int>q[10001];
vector<int>p[10001];
int liantong[100001],va[100001],sum[100001],dfn[100001],low[100001],st[100001];
bool inst[100001],vis[100001];
int x[100001],y[100001],dis[100001],ans;
void tarjan(int u)
{
    dfn[u]=++dfn[0];
    low[u]=dfn[u];
    st[++st[0]]=u;
    inst[u]=1;
    for(int i=0;i<q[u].size();++i)
    {
        int v=q[u][i];
        if(!dfn[v])
            tarjan(v);
        if(inst[v])
            low[u]=min(low[v],low[u]);
    }
    if(dfn[u]==low[u])
    {
        liantong[0]++;
        while(st[st[0]]!=u)
        {
            liantong[st[st[0]]]=liantong[0];
            sum[liantong[0]]+=va[st[st[0]]];
            inst[st[st[0]]]=0;
            st[0]--;
        }
        liantong[st[st[0]]]=liantong[0];
            sum[liantong[0]]+=va[st[st[0]]];
            inst[st[st[0]]]=0;
            st[0]--;
    }
}
void spfa(int x)//蒟蒻的spfa
{
    memset(dis,0,sizeof(dis));
    memset(vis,0,sizeof(vis));
    queue<int>hh;
    dis[x]=sum[x];
    vis[x]=1;
    hh.push(x);
    while(!hh.empty())
    {
        int u=hh.front();
        hh.pop();
        vis[u]=0;
        for(int i=0;i<p[u].size();++i)
        {
            if(dis[p[u][i]]<dis[u]+sum[p[u][i]])
            {
                dis[p[u][i]]=dis[u]+sum[p[u][i]];
            }
            if(!vis[p[u][i]])
            {
                vis[p[u][i]]=1;
                hh.push(p[u][i]);
            }
        }
    }
    for(int i=1;i<=liantong[0];++i)
        ans=max(ans,dis[i]);
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        scanf("%d",&va[i]);
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d",&x[i],&y[i]);
        q[x[i]].push_back(y[i]);
    }
    for(int i=1;i<=n;++i)
    {
        if(!dfn[i])
            tarjan(i);
    }
    for(int i=1;i<=m;++i)
    {
        if(liantong[x[i]]!=liantong[y[i]])
        {
            p[liantong[x[i]]].push_back(liantong[y[i]]);
        }
    }
    for(int i=1;i<=liantong[0];++i)
    spfa(i);
    printf("%d",ans);
    return 0;
}