【tarjan缩点+最长路】luogu P3387

题目背景

缩点+DP

题目描述

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

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

输入输出格式

输入格式:

第一行,n,m

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

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

输出格式:

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

输入输出样例

输入样例#1: 复制

2 2
1 1
1 2
2 1

输出样例#1: 复制

2

说明

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

先缩点,把在同一个强连通分量里的点合并起来。然后重新建图,每个点跑一下最长路就行了。就当练习模板了。

注意SPFA的时候用的是点权,不是边权。重新建图的时候要把原来的图重置一下。

#include<bits/stdc++.h>
using namespace std;
const int maxn=100010;
int Head[maxn],Next[maxn],V[maxn],W[maxn],val[maxn],x[maxn],y[maxn];
int st[maxn],ins[maxn],dfn[maxn],low[maxn],belong[maxn],dis[maxn],vis[maxn];
int cnt=0,tot=0,sz=0,top=0,ans=0,n,m,u,v;
void add(int u,int v){
	++cnt;
	Next[cnt]=Head[u];
	V[cnt]=v;
	Head[u]=cnt;
}
void tarjan(int u){
	st[++top]=u,ins[u]=1;
	dfn[u]=low[u]=++sz;
	for(int i=Head[u];i;i=Next[i]){
		int v=V[i];
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(ins[v]) low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		int j;tot+=1;
		while(j!=u){
			j=st[top--];
			ins[j]=0;
			belong[j]=tot;
			W[tot]+=val[j];
		}
	}
}
void SPFA(int s){
	queue<int> Q;
	memset(dis,0,sizeof(dis));
	memset(vis,0,sizeof(vis));
	dis[s]=W[s];
	Q.push(s);
	while(!Q.empty()){
		int u=Q.front();
		Q.pop();
		vis[u]=0;
		for(int i=Head[u];i!=-1;i=Next[i]){
			int v=V[i];
			if(dis[v]<dis[u]+W[v]){
				dis[v]=dis[u]+W[v];
				if(!vis[v]){
					vis[v]=1;
					Q.push(v);
				}
			}
		}
	}
	for(int i=1;i<=tot;++i)
		ans=max(ans,dis[i]);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",&val[i]);
	for(int i=1;i<=m;++i){
		scanf("%d%d",&u,&v);
		add(u,v);
		x[i]=u,y[i]=v;
	}
	for(int i=1;i<=n;++i){
		if(!dfn[i])
			tarjan(i);
	}
	memset(Head,-1,sizeof(Head));
	memset(Next,-1,sizeof(Next));
	memset(V,0,sizeof(V));
	cnt=0;
	for(int i=1;i<=m;++i)
		if(belong[x[i]]!=belong[y[i]])
			add(belong[x[i]],belong[y[i]]);
	for(int i=1;i<=tot;++i) SPFA(i);
	printf("%d\n",ans);
}

猜你喜欢

转载自blog.csdn.net/g21wcr/article/details/83021386