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

算法:Tarjan缩点+DAGdp

Solution

正如题目说明所说的那样,我们先将整个图tarjan缩点后变成一张DAG,然后在这张DAG上dp。关键是如何dp?方法有多种,可以先求出DAG的拓扑序,再按照拓扑序从后往前dp,也可以直接标记当前这个儿子是否dp过,若没有dp则dp,dp过了就直接用dp的值进行更新,状态转移方程很容易,即为 dp[ i ]=dp[ i ] + max{ dp[ son[ i ] ] }。

Code

#include 
#include
#include
#define N 10010
#define M 100010
using namespace std;
int ver[M],next[M],head[N],dfn[N],low[N],a[N],vc[M],nc[M],hc[N],tc,f[N];
int n,m,tot,num,top,cnt,s[N],ins[N],c[N],bz[N];
void add(int x,int y){ver[++tot]=y,next[tot]=head[x],head[x]=tot;}
void add_c(int x,int y){vc[++tc]=y,nc[tc]=hc[x],hc[x]=tc;}
void tarjan(int x){
	dfn[x]=low[x]=++num;ins[x]=1;s[++top]=x;
	for(int k=head[x];k;k=next[k]){
		int y=ver[k];
		if(!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if(ins[y]) low[x]=min(low[x],dfn[y]);
	} 
	if(dfn[x]==low[x]){
		cnt++;int y;
		do{
			y=s[top--],ins[y]=0;
			c[y]=cnt;f[cnt]+=a[y];
		}while(x!=y);
	}
}
void dp(int x){
	int ma=0;bz[x]=1;
	for(int i=hc[x];i;i=nc[i]){
		int y=vc[i];
		if(!bz[y]) dp(y);
		ma=max(ma,f[y]);
	}
	f[x]+=ma;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
	for(int x=1;x<=n;x++){
		for(int i=head[x];i;i=next[i]){
			int y=ver[i];
			if(c[x]==c[y]) continue;
			add_c(c[x],c[y]);
		}
	}
	for(int i=1;i<=n;i++){
		if(!bz[i]){
			dp(i);
			f[0]=max(f[0],f[i]);
		}
	}
	printf("%d",f[0]);
	return 0;
}


作者:zsjzliziyang 
QQ:1634151125 
转载及修改请注明 
本文地址:https://blog.csdn.net/zsjzliziyang/article/details/87023093

你可能感兴趣的:(tarjan,题目,dp)