第一次写缩点是用vector存边,因此现在用链前存边再写一遍

传送门P3387 【模板】缩点

题意分析

(1)很容易发现,由于每一个点可以被经过多次,那么同一个强连通分量中的每一个点都可以互相到达,因此可以看作一个点。
(2)缩点后,整张图一定变成了一个DAG
证明:反证即可,假设不是DAG,则有环存在,环是强连通分量,而缩点后把每一个强连通分量都缩成了一个点,新图中显然没有强连通分量,假设不成立,则新图一定是DAG
(3)然后就是求一条最长路径。
方法一:用spfa跑最长路
方法二:拓扑排序dp:
无后效性的证明
dp[i]是以i为终点的路径的最大点权和
因为拓扑序中可以保证对于图中∀边(u,v),u一定在v的前面,这样的话当我需要更新dp[v]的值时,需要用到的dp[u]的值都已经处理过了,dp[v]的值就不会再被它后面遍历到的点的dp值影响,正因如此,这也就保证了dp的无后效性。
(4)注意ans=max(anx,dp[i]),因为每个点都可能成为点权和最大的路径的终点

code

几个小细节
(1)为了方便,把缩点后都每个点编号从tot=n+1开始,这样就可以用原来的addline函数
(2)一定要在拓扑的同时dp
(3)我更喜欢在重新建图的过程中遍历m条边,其实遍历每个点的出边也可以,毕竟最开始每个边我们只建了一次,本题中一定满足不重不漏。
但是某些题目中可能会需要建双向边,这个时候就要注意不要把一条边建两次了,否则新建成的图会有环,而拓扑排序在一个有环的图里会死循环。

#include<bits/stdc++.h>
using namespace std;
const int maxm=3e5+5,maxn=3e4+5;
int n,m,head[maxm],ecnt=-1,sav[maxn];
int dfn[maxn],low[maxn],sum[maxn],tot,Time,top,col[maxn],indu[maxn],s[maxn],dp[maxn];
bool vis[maxn];
struct mint
{
	int nxt,u,v;	
}e[maxm];
inline void add(int u,int v)
{
	e[++ecnt].nxt=head[u];
	e[ecnt].u=u;
	e[ecnt].v=v;
	head[u]=ecnt;	
}
void tarjan(int now)
{
	dfn[now]=low[now]=++Time;
	s[++top]=now;
	for(int i=head[now];~i;i=e[i].nxt)
	{
		int v=e[i].v;
		if(!dfn[v])
		{
			tarjan(v);
			low[now]=min(low[now],low[v]);	
		}	
		else if(col[v]==0) low[now]=min(low[now],dfn[v]);
	}
	if(low[now]==dfn[now])
	{
		int cur;
		tot++;
		do
		{
			cur=s[top--];
			col[cur]=tot;
			sum[tot]+=sav[cur];
			vis[cur]=false;
		}while(cur!=now);
	}
}
void topo()
{
	queue<int> q;
	for(int i=n+1;i<=tot;++i) 
	{
		if(indu[i]==0) q.push(i);
	}	
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		dp[u]=max(dp[u],sum[u]);
		for(int i=head[u];~i;i=e[i].nxt)
		{
			int v=e[i].v;
			indu[v]--;
			dp[v]=max(dp[v],dp[u]+sum[v]);
			if(indu[v]==0) q.push(v);
		}
	}
}
int main()
{
	memset(head,-1,sizeof(head));
	int a,b;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",&sav[i]);
	for(int i=1;i<=m;++i)
	{
		scanf("%d%d",&a,&b);
		add(a,b);
	}
	tot=n;
	for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
	for(int i=0;i<=m-1;++i)
	{
		int a=e[i].u,b=e[i].v;
		if(col[a]!=col[b])
		{
			indu[col[b]]++;
			add(col[a],col[b]);	
		}
	}
	topo(); 
	int ans=0;
	for(int i=n+1;i<=tot;++i) ans=max(ans,dp[i]);
	printf("%d",ans);
	return 0;	
}