tarjan 缩点(模板)

描述:

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

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

思路:

  tarjan 的模板之一——缩点。先利用 tarjan 出图中的强连通分量及大小(点的权值),然后遍历所有点,重新构图(←重点),根据 topo DP一下,就可得出图中最大的权值和。

标程:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<string>
#include<cstdlib>
#include<stack>
#include<vector>
#include<queue>
#include<deque>
#include<map>
#include<set>
using namespace std;
#define maxn 1000005
int n,m,cnt1,num,top,cnt2;//cnt1 作原图的前向星,cnt2 作新图的 
int ins[maxn],head[maxn],nu[maxn],dfn[maxn],low[maxn];//nu 用来去除重边(重构图用),ins 记录强连通分量的大小 
int st[maxn],co[maxn];//栈只为了表示此时是否有父子关系,co判断该点是否在栈中 
int h[maxn],in[maxn],dis[maxn];//h相当于新图前向星的head,in统计点的入度,dis记录权值和(in,dis都做topo排序用) 
int ans=0;//统计答案 
struct hh
{
    int to,next,from;//from,to有可以分别记录边的起点和终点 
}t1[maxn],t2[maxn];//t1原图,t2新图 
inline  int read()
{
    int kr=1,xs=0;
    char ls;
    ls=getchar();
    while(!isdigit(ls))
    {
        if(!(ls^45))
            kr=-1;
        ls=getchar();
    }
    while(isdigit(ls))
    {
        xs=(xs<<1)+(xs<<3)+(ls^48);
        ls=getchar();
    }
    return xs*kr;
}
inline void add(int x,int y)
{
    t1[++cnt1].next=head[x];
    t1[cnt1].from=x;
    t1[cnt1].to=y;
    head[x]=cnt1;
}//存原图 
inline void tarjan(int x)
{
    low[x]=dfn[x]=++num;
    st[++top]=x;co[x]=1;
    for (int i=head[x];i;i=t1[i].next)
    {
        int v=t1[i].to;
        if(!dfn[v]) 
        {
        tarjan(v);
        low[x]=min(low[x],low[v]);
        }
        else if(co[v])
        {
            low[x]=min(low[x],low[v]);
        }
    }
    if (dfn[x]==low[x])
    {
        int y;
        while(y=st[top])
        {
            nu[y]=x;//表示:可以从x直接到达y(单向) 
            co[y]=0;//y出栈(记录清除) 
            if(x==y) break;
            ins[x]+=ins[y];//合并两个强连通分量
            --top;
        }
        --top;
    }
}//日常操作 
inline void topo()
{
    queue <int> q;
    int tot=0;
    for (int i=1;i<=n;i++)
        if (nu[i]==i&&!in[i])//该点自己到达自己,且入度为0,表示为一个被缩为一点的强连通分量(且为起始点) 
        {
            q.push(i);
            dis[i]=ins[i];
        } 
    while (!q.empty())//依次取出所有的起点(入度为0的点) 
    {
        int k=q.front();q.pop();
        for (int i=h[k];i;i=t2[i].next)//遍历可以到达的点 
        {
            int v=t2[i].to;
            dis[v]=max(dis[v],dis[k]+ins[v]);//更新答案 
            in[v]--;//入度-- 
            if(in[v]==0)    q.push(v);//减到这个点入度为0,扔进队列,下次再取出作为起点 
        }
    }
    for (int i=1;i<=n;i++)
        ans=max(ans,dis[i]);//更新最终答案 
}
int main()
{
    n=read();m=read();
    for (int i=1;i<=n;i++)
        ins[i]=read();//初始每个点认为是一个强连通分量(假装是) 
    for (int i=1;i<=m;i++)
    {
        int u,v;
        u=read();v=read();
        add(u,v);
    }
    for (int i=1;i<=n;i++)
        if(!dfn[i])
            tarjan(i);
    for (int i=1;i<=m;i++)
    {
        int x=nu[t1[i].from],y=nu[t1[i].to];
        if (x!=y)//←可以去除重边 
        {
            t2[++cnt2].next=h[x];
            t2[cnt2].to=y;
            t2[cnt2].from=x;
            h[x]=cnt2;//重构新图 
            in[y]++;
        }
    }
    topo();//topo 排序 
    printf("%d",ans);//输出 
return 0;
}

 

posted @ 2018-09-27 14:59  落笔映惆怅丶  阅读(294)  评论(0编辑  收藏  举报