1. 程式人生 > >【tarjan縮點+最長路】luogu P3387

【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);
}