缩点(tarjan求强连通分量)

P3387 【模板】缩点

题目背景

缩点+\(DP\)

题目描述

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

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

输入输出格式

输入格式:
第一行,\(n,m\)

第二行,\(n\)个整数,依次代表点权

第三至\(m+2\)行,每行两个整数\(u,v\),表示\(u->v\)有一条有向边

输出格式:
共一行,最大的点权之和。

说明

\(n<=10^4,m<=10^5\),点权\(<=1000\)

算法:\(Tarjan\)缩点+\(DAGdp\)


重点在于tarjan求解强连通分量。

定义:

  1. 有向图强连通分量:在有向图\(G\)中,如果两个顶点\(v_i,v_j\)\((v_i>v_j)\)有一条从\(v_i\)\(v_j\)的有向路径,同时还有一条从\(v_j\)\(v_i\)的有向路径,则称两个顶点强连通。
  2. 如果有向图\(G\)的每两个顶点都强连通,称\(G\)是一个强连通图。
  3. 有向图的极大强连通子图,称为强连通分量。
    说明:单个点也可以是强联通分量。

对每个点\(i\)维护两个值,\(dfn[i]\)\(low[i]\),前者代表时间戳,表示\(i\)是第几个被访问的(用全局变量维护),后者表示最小时间,即\(i\)能到达的点中\(dfn\)最小者。

访问到点\(i\)时,我们先把\(i\)放入栈中,然后令\(dfn[i]=low[i]=\)当前时间,去遍历\(i\)点能到达的点(值得注意的是,这个点必须在栈里面),看是否能够更新\(low[i]\)

栈:我们求解某次强连通分量时,存储在一个强连通分量的点集。点\(i\)某个能遍历的点\(j\)在栈里面对应的即是\(j\)\(i\)是联通的。

\(i\)能到达的点遍历完毕时,若\(dfn[i]!=low[i]\),即它能到达一个更早的时间点,作为这个强联通分量的使点。
\(dfn[i]==low[i]\),则这个点可以作为某块强连通分量的开始点。

我们把栈中的元素取出来,直到栈顶为\(i\),则这些取出来的点连同\(i\)即构成了一块强连通分量。

感性认识:用两个值维护了点的相互连通性。

我们把点缩好以后,做拓扑排序dp就行了。


code:

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int M=100010;
int min(int x,int y) {return x<y?x:y;}
int max(int x,int y) {return x>y?x:y;}
struct Edge{int to,next;}edge0[M*2],edge[M*2];
int head0[M],cnt0=0,head[M],cnt=0;
int c0[M],c[M],n,m;
void add0(int u,int v){edge0[++cnt0].next=head0[u];edge0[cnt0].to=v;head0[u]=cnt0;}
void add(int u,int v){edge[++cnt].next=head[u];edge[cnt].to=v;head[u]=cnt;}
int s[M],tot=0;
void push(int x){s[++tot]=x;}
void pop(){tot--;}
int in[M],ha[M],cntt=0,dfn[M],low[M],time=0,vis[M];
void tarjan(int now)
{
    push(now);
    vis[now]=1;
    dfn[now]=low[now]=++time;
    for(int i=head0[now];i;i=edge0[i].next)
    {
        int v=edge0[i].to;
        if(!dfn[v]) {tarjan(v);low[now]=min(low[now],low[v]);}
        else if(vis[v]) low[now]=min(low[now],dfn[v]);
    }
    if(dfn[now]==low[now])
    {
        cntt++;
        while(s[tot]!=now)
        {
            vis[s[tot]]=0;
            c[cntt]+=c0[s[tot]];
            ha[s[tot]]=cntt;
            pop();
        }
        ha[s[tot]]=cntt;
        vis[s[tot]]=0;
        c[cntt]+=c0[s[tot]];
        pop();
    }
}
queue <int > q;
int dp[M],ans=0;
void topo()
{
    for(int i=1;i<=cntt;i++)
        if(!in[i])
        {
            dp[i]=c[i];
            ans=max(dp[i],ans);
            q.push(i);
        }
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=head[u];i;i=edge[i].next)
        {
            int v=edge[i].to;
            in[v]--;
            dp[v]=max(dp[u]+c[v],dp[v]);
            if(!in[v]) {ans=max(ans,dp[v]);q.push(v);}
        }
    }
}
int main()
{
    int u,v;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",c0+i);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&u,&v);
        add0(u,v);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i]) tarjan(i);
    for(int i=1;i<=n;i++)
        for(int j=head0[i];j;j=edge0[j].next)
        {
            int v=edge0[j].to;
            if(ha[v]!=ha[i])
            {
                add(ha[i],ha[v]);
                in[ha[v]]++;
            }
        }
    topo();
    printf("%d\n",ans);
    return 0;
}

2018.6.6

猜你喜欢

转载自www.cnblogs.com/ppprseter/p/9146453.html
今日推荐