洛谷P3387 【模板】缩点 tarjan缩点+拓扑排序

题目背景
缩点+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,0<=点权<=1000

算法:Tarjan缩点+DAGdp

解法:tarjan缩点+拓扑排序

  • 由于点权值只能取得一次,再次取为0,所以我们可以考虑缩点,这样之后,我们取一个点,就相当于把这个环上的所有点都给取到了,我们可以再次拓扑排序,在这上面进行DP

AC代码

#include<cstdio>
#include<queue>
#include<stack>
#include<cstring>
#define si 10005
#define re register int
using namespace std;
struct edge {
	int nex,fro,to;
}e[si*10];
struct node {
	int nex,to;
}t[si*10];
int n,m,cnt,d[si],head[si],ru[si],dis[si];
int num,tot,dfn[si],low[si],a[si],sum[si];
bool v[si]; stack<int> s; queue<int> q;
inline int read() {
	int x=0,cf=1;
	char ch=getchar();
	while(ch<'0'||ch>'9') {
		if(ch=='-') cf=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return x*cf;
}
inline void add1(int x,int y) {
	e[++cnt].fro=x,e[cnt].to=y,e[cnt].nex=head[x],head[x]=cnt;
}
inline void add2(int x,int y) {
	t[++cnt].to=y,t[cnt].nex=head[x],head[x]=cnt;
}
inline int min(int A,int B) { return A<B?A:B; }
inline int max(int A,int B) { return A>B?A:B; }
inline void tarjan(int x) {
	dfn[x]=low[x]=++num;
	s.push(x); v[x]=true;
	for(re i=head[x];i;i=e[i].nex) {
		int y=e[i].to;
		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]) {
		tot++;
		while(x!=s.top()) {
			a[s.top()]=tot;
			sum[tot]+=d[s.top()];
			v[s.top()]=false; s.pop();
		}
		a[x]=tot; sum[tot]+=d[x];
		v[x]=false; s.pop();
	}
	return;
}
inline int topo() {
	for(re i=1;i<=tot;i++) {
		if(!ru[i]) {
			q.push(i);
			dis[i]=sum[i];
		}
	}
	while(q.size()) {
		int x=q.front(); q.pop();
		for(re i=head[x];i;i=t[i].nex) {
			int y=t[i].to; ru[y]--;
			dis[y]=max(dis[y],dis[x]+sum[y]);
			if(!ru[y]) q.push(y);
		}
	}
	int ans=0;
	for(re i=1;i<=n;i++) {
		ans=max(ans,dis[i]);
	}
	return ans;
}
int main() {
	n=read(),m=read();
	for(re i=1;i<=n;i++) d[i]=read();
	for(re i=1;i<=m;i++) {
		int x=read(),y=read();
		add1(x,y);
	}
	for(re i=1;i<=n;i++) {
		if(!dfn[i]) tarjan(i);
	}
	memset(head,0,sizeof(head)),cnt=0;
	for(re i=1;i<=m;i++) {
		int x=a[e[i].fro],y=a[e[i].to];
		if(x!=y) add2(x,y),ru[y]++;
	}
	printf("%d",topo());
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值