zl程序教程

您现在的位置是:首页 >  其他

当前栏目

洛谷 P3387 【模板】缩点

模板 洛谷
2023-09-11 14:20:14 时间

洛谷 P3387 【模板】缩点

洛谷传送门

题目背景

缩点+DP

题目描述

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

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

输入格式

第一行两个正整数 n,mn,m

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

第三至 m+2m+2 行,每行两个整数 u,vu,v,表示一条 u\rightarrow vuv 的有向边。

输出格式

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


题解:

缩点之后就变成DAG了,就可以拓扑DP了。

代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int maxn=1e4+4;
const int maxm=1e5+5;
int n,m;
int a[maxn];
int tot,head[maxn],nxt[maxm],to[maxm],from[maxm];
int dfn[maxn],low[maxn];
int st[maxn],top,cnt;
bool v[maxn];
int id[maxn],rudu[maxn],dp[maxn];
int tot1,head1[maxn],nxt1[maxm],to1[maxm];
void add(int x,int y)
{
	to[++tot]=y;
	nxt[tot]=head[x];
	from[tot]=x;
	head[x]=tot;
}
void add1(int x,int y)
{
	to1[++tot1]=y;
	nxt1[tot1]=head1[x];
	head1[x]=tot1;
}
void tarjan(int x)
{
	dfn[x]=low[x]=++cnt;
	st[++top]=x;
	v[x]=1;
	for(int i=head[x];i;i=nxt[i])
	{
		int y=to[i];
		if(!dfn[y])
		{
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if(v[y])
			low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x])
	{
		int now;
		do
		{
			now=st[top--];
			id[now]=x;
			v[now]=0;
			a[x]+=a[now];
		}while(now!=x);
	}
}
queue<int> q;
int topsort()
{
	for(int i=1;i<=n;i++)
		if((id[i]==i)&&(!rudu[i]))
		{
			q.push(i);
			dp[i]=a[i];
		}
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		for(int i=head1[x];i;i=nxt1[i])
		{
			int y=to1[i];
			dp[y]=max(dp[y],dp[x]+a[y]);
			rudu[y]--;
			if(!rudu[y])
				q.push(y);
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++)
		ans=max(ans,dp[id[i]]);
	return ans;
}
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 i=1;i<=m;i++)
	{
		int x=from[i],y=to[i];
		if(id[x]!=id[y])
		{
			add1(id[x],id[y]);
			rudu[id[y]]++;
		}
	}
	printf("%d\n",topsort());
	return 0;
}