洛谷P3387 tarjan缩点+DP

题目描述

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

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

tarjan简介

强连通分量:有向图中,若任意两节点均能通过若干有向边达到对面,则这个有向图所有节点为强连通分量。例子:环。
tarjan算法是用来求强连通分量的算法。该算法把所有经过的节点压入一个栈中,若到达的节点已经在栈中则说明已经发现了强连通分量。
一个点本身是一个强连通分量。

tarjan写法

出现的数组有:
dfn:到达某点的时间,用tim维护,每次遍历时++tim
stack:使用的栈,栈顶为top
vis:记录某点是否在栈中
low:强连通分量中最早到达的节点的次序号
belong:记录节点属于的强连通分量的编号

tarjan过程:

Tarjan(x) {
	更新vis,dfn等信息
	遍历每个用边连接的节点nxt {
		若nxt仍未被访问(即dfn[nxt]==0{
			递归tarjan(nxt)
			更新low信息
		} else {
			若nxt在栈中(即vis[nxt]{
				更新low信息
			}
		}
	}
	若x是所在强连通分量中的第一个节点 {
		处理强连通分量中vis信息
		处理强连通分量中belong信息
		把强连通分量中其他节点的点权加到x中(准备缩点)
	}
}

缩点写法

遍历之前的所有边,找到两端点的强连通分量编号(belong值),在两强连通分量之间加边。

代码

剩下用拓扑排序+DP实现求距离即可。
代码:

#include<stdio.h>
#include<cstring>
#include<algorithm>
using namespace std;
int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9')
	{
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int Size=10005;
int n,m,p[Size];
struct Edge
{
	int u,v,next;
} w[100001];		//链式前向星存边,u连到v 
int cnt,head[Size];
void AddEdge(int u,int v)	//加边 
{
	cnt++;
	w[cnt].u=u;
	w[cnt].v=v;
	w[cnt].next=head[u];
	head[u]=cnt;
}
//tarjan算法中用到的各数组 
int tim,top,dfn[Size],stack[Size],belong[Size],low[Size];
bool vis[Size];
void tarjan(int x)
{
	vis[x]=true;
	low[x]=dfn[x]=++tim;
	stack[++top]=x;
	for(int i=head[x]; i; i=w[i].next)
	{
		int nxt=w[i].v;
		if(!dfn[nxt])
		{
			tarjan(nxt);		//递归对nxt求tarjan 
			low[x]=min(low[x],low[nxt]);
		} else if(vis[nxt]) {
			low[x]=min(low[x],low[nxt]);
		}
	}
	if(dfn[x]==low[x])
	{
		int lst=stack[top];
		while(top--)
		{
			belong[lst]=x;
			vis[lst]=false;
			if(lst==x)
			{
				return;
			}
			p[x]+=p[lst];
			lst=stack[top];
		}
	}
}
int indegree[Size];		//节点入度 
void init()				//预处理输入 
{
	n=read();
	m=read();
	for(int i=1; i<=n; i++)
	{
		p[i]=read();
	}
	for(int i=1; i<=m; i++)
	{
		int u=read();
		int v=read();
		AddEdge(u,v);
	}
}
void Shrink()			//缩点 
{
	for(int i=1; i<=n; i++)
	{
		if(!dfn[i])
		{
			tarjan(i);
		}
	}
	memset(head,0,sizeof(head));
	cnt=0;
	for(int i=1; i<=m; i++)
	{
		int b1=belong[w[i].u];
		int b2=belong[w[i].v];
		if(b1!=b2)
		{
			AddEdge(b1,b2);
			indegree[b2]++;
		}
	}
}
int Queue[Size],dist[Size];
void TopoSort()		//拓扑排序+DP 
{
//	memset(dist,0x3f,sizeof(dist));
	int hd=0,tl=0;
	for(int i=1; i<=n; i++)
	{
		if(belong[i]==i && !indegree[i])
		{
			Queue[++tl]=i;
			dist[i]=p[i];
		}
	}
	while(hd<=tl)
	{
		int x=Queue[++hd];
		for(int i=head[x]; i; i=w[i].next)
		{
			int nxt=w[i].v;
			indegree[nxt]--;
			dist[nxt]=max(dist[nxt],dist[x]+p[nxt]);
//			printf("v=%d dist[v]=%d\n",nxt,dist[nxt]);
			if(!indegree[nxt])
			{
				Queue[++tl]=nxt;
			}
		}
	}
}
int main()
{
	init();
	Shrink();
	TopoSort();
	int ans=0;
	for(int i=1; i<=n; i++)
	{
		if(dist[i]>ans)
		{
			ans=dist[i];
		}
	}
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值