P3387 Tarjan 缩点

【模板】缩点

题目描述

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

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

输入格式

第一行两个正整数 \(n,m\)

第二行 \(n\) 个整数,其中第 \(i\) 个数 \(a_i\) 表示点 \(i\) 的点权。

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

输出格式

共一行,最大的点权之和。

样例 #1

样例输入 #1

2 2
1 1
1 2
2 1

样例输出 #1

2

提示

对于 \(100\%\) 的数据,\(1\le n \le 10^4\)\(1\le m \le 10^5\)\(0\le a_i\le 10^3\)

参考注释 + https://www.luogu.com.cn/problem/solution/P3387 第二篇题解

关键是dfn low stack 以及对图的缩点后的重建

#include<bits/stdc++.h>
#define maxn 100001
#define maxm 500001
using namespace std;
struct node{
    int to,next,from;
}edge[maxm];
queue<int>q;
vector<int>cb[maxn];
vector<int>rdr[maxn];
int ans[maxn],totq,x,y,v,in[maxn],u,n,m,sum,belong[maxn],dis_[maxn],dis[maxn];
int dfn[maxn],low[maxn],f[maxn],times,cntqq;
int stack_[maxn],head[maxm],visit[maxn],cnt,tot,pt;
void add(int x,int y)		//建边
{
	++cntqq;
    edge[cntqq].next=head[x];
    edge[cntqq].from=x;
    edge[cntqq].to=y;
    head[x]=cntqq;
}
void topsort()				//拓扑排序
{
	for(int i=1;i<=tot;i++)	//初始化 
    {
        if(in[i]==0)
        q.push(i);			//入度为0的都进队列 
    }
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        ans[++totq]=u;
        for(int i=0;i<cb[u].size();i++)
        {
            v=cb[u][i];
            in[v]--;
            if(in[v]==0)q.push(v);
        }
    }
}
void tarjan(int x)//tarjan求强连通分量
{
    dfn[x]=low[x]=++times;//dfn[i]:i点的进入时间
						  //low[i]:从i点出发 所能访问到的最早进入时间 
    stack_[++pt]=x;
    visit[x]=1;//x已访问并入栈 
   	for(int i=head[x];i;i=edge[i].next)
    {
        if(!dfn[edge[i].to])//未访问 
        {
           	tarjan(edge[i].to);
            low[x]=min(low[x],low[edge[i].to]);//之所以是两个low 可以理解为 dfs递归 
       	}
       	else 
        	if(visit[edge[i].to])//已入栈 
       		 	low[x]=min(low[x],dfn[edge[i].to]);//没有递归 to点的times就是dfn[to] 
    }
    if(low[x]==dfn[x])//说明是强连通分量 
   	{
   		tot++;//强连通分量编号 
   		while(1)
       	{
       		//将强连通分量 内 所有点都belong to 所属强连通分量编号(缩成一个点) 
       		belong[stack_[pt]]=tot;	//pt所在的强连通分量编号,等于前面讲的belong 
       		dis_[tot]+=dis[stack_[pt]];	//强连通分量权值累加 
       		visit[stack_[pt]]=0;//标记出栈 
			pt--;
       		if(x==stack_[pt+1])break;//若x都已经出栈了 这个强连通分量内的所有点就缩完了 就break 
    	}
    }
}
signed main()
{
    memset(head,0,sizeof(head));
    int n,m,x,y;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&dis[i]);
    for(int i=1;i<=m;i++)
	{
        scanf("%d%d",&x,&y);
       	add(x,y);
    }
    for(int i=1;i<=n;i++)
		if(!dfn[i])//都要遍历完  图论基本上都是这样 二分图染色也是  多个连通块 
			tarjan(i); 	//tarjan 
    for(int i=1;i<=cntqq;i++)
	{			//拓扑建边
        if(belong[edge[i].from]!=belong[edge[i].to])//from->to 两端点不在一个强连通分量内就给新图加边 
        {
            x=belong[edge[i].from];//对应强连通分量编号 
			y=belong[edge[i].to];//~
			in[y]++;
			cb[x].push_back(y);
			rdr[y].push_back(x);
        }
    }
    topsort();
    for(int i=1;i<=tot;i++)				//dp
    {
        int w=ans[i];//拓扑序 
        f[w]=dis_[w];
        for(int j=0;j<rdr[w].size();j++)
        f[w]=max(f[w],f[rdr[w][j]]+dis_[w]);//f[w]=max(f[w],f[v]+dis_[w]) v: v--->w  即 rdr[w][j] 
    }
    for(int i=1;i<=tot;i++)				//最后统计答案 
    	sum=max(f[i],sum);
    printf("%d\n",sum);
    return 0;
}
posted @ 2023-04-18 22:05  N0zoM1z0  阅读(6)  评论(0编辑  收藏  举报