• 【Luogu3381】【模板】缩点


    本文同步发表于https://www.zybuluo.com/Gary-Ying/note/1235385

    题目描述

    给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
    允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。

    输入输出格式

    输入格式:

    第一行,n,m
    第二行,n个整数,依次代表点权
    第三至m+2行,每行两个整数u,v,表示u->v有一条有向边

    输出格式:

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

    样例输入

    2 2
    1 1
    1 2
    2 1
    

    样例输出

    2
    

    数据范围

    (n<=10^4),(m<=10^5),(点权<=1000)

    题解

    算法

    正如题目名称所说,这道题需要使用缩点的技巧,那么这是为什么呢?
    我们可以把一个强连通分量近似看作一个环,由于这道题中边权只能取1次,每个点和每条边可以经过多次,所以一个强连通分量中的点一定可以全部取到。并且强连通分量中各点的出边可以看做是同一点的出边。于是我们可以高兴地说:

    这道题中一个强连通分量相当于一个点,我们可以缩点了!
    

    缩点以后图就变成了DAG(有向无环图),对于有向无环图,注定是要与拓扑排序为伴的。DP转移方程如下:(dp[v]表示走到v点的最大路径长度,a[v]表示v这个强连通分量中所有点的点权之和)

    [dp[v]=max{dp[u]+a[v]} ]

    转移的条件是存在边(u,v)。用拓扑排序转移DP即可(避免后效性,保证处理v的转移时u的转移已经处理)。

    易错点(其实就是我错的)

    1、第37行:if (vis[v])low[u] = min(low[u], dfn[v]);
    2、

    for (int i = 1; i <= n; ++i)
            if (!dfn[i])tarjan(i);
    

    3、 第84行:addedge1(color[i],color[v]);

    Code

    #include<iostream>
    #include<cstdio>
    using namespace std;
    
    const int maxn = 10007;
    const int maxm = 100007;
    int n, m;
    int c[maxn], a[maxn];
    int edgenum, head[maxn], Next[maxm], vet[maxm];
    int edgenum1, head1[maxn], Next1[maxm], vet1[maxm];
    int dfn[maxn], low[maxn], stamp;
    int stack[maxn], top, color[maxn], num;    
    int init[maxn];
    bool vis[maxn];
    
    void addedge(int u, int v){
    	++edgenum;
    	vet[edgenum] = v;
    	Next[edgenum] = head[u];
    	head[u] = edgenum;
    }
    void addedge1(int u, int v){
    	++edgenum1;
    	vet1[edgenum1] = v;
    	Next1[edgenum1] = head1[u];
    	head1[u] = edgenum1;
    	++init[v];
    }
    void tarjan(int u){
    	dfn[u] = low[u] = ++stamp;
    	stack[++top] = u; vis[u] = true;
    	for (int e = head[u]; e; e = Next[e]){
    		int v = vet[e];
    		if (!dfn[v]){
    			tarjan(v);
    			low[u] = min(low[u], low[v]);
    		}else if (vis[v])low[u] = min(low[u], dfn[v]);
    	}
    	if (dfn[u] == low[u]){
    		color[u] = ++num; vis[u] = false;
    		while (stack[top] != u) vis[stack[top]] = false, color[stack[top--]] = num;
    		--top;
    	}
    }
    
    void tp(){
    	int head = 0, tail = -1, que[maxn], dp[maxn];
    	for (int i = 1; i <= num; ++i) {
    		if (init[i] == 0){
    			que[++tail] = i;
    		}
    		dp[i] = a[i];
    	}
    	while (head <= tail){
    		int u = que[head]; ++head;
    		for (int e = head1[u]; e; e = Next1[e]){
    			int v = vet1[e];
    			--init[v];
    			if (init[v] == 0) que[++tail] = v;
    			dp[v] = max(dp[v], dp[u] + a[v]);
    		}
    	}
    	int ans = 0;
    	for (int i = 1; i <= num; ++i)
    		if (dp[i] > ans) ans = dp[i];
    	printf("%d
    ", ans);
    }
    
    int main(){
    	scanf("%d%d", &n, &m);
    	for (int i = 1; i <= n; ++i)
    		scanf("%d", &c[i]);
    	for (int i = 1; i <= m; ++i){
    		int u, v;
    		scanf("%d%d", &u, &v);
    		addedge(u,v);
    	}
    	for (int i = 1; i <= n; ++i)
    		if (!dfn[i])tarjan(i);
    	for (int i = 1; i <= n; ++i){
    		for (int e = head[i]; e; e = Next[e]){
    			int v = vet[e];
    			if (color[i]!=color[v]){
    				addedge1(color[i],color[v]);
    			}
    		}
    		a[color[i]] += c[i];
    	}
    	tp();
    	return 0;
    }
    
    
    来自YJZoier的博客 转载请注明出处
  • 相关阅读:
    盛最多水的容器
    除自身以外数组的乘积
    组合总和
    旋转图像
    找到所有数组中消失的数字
    RSA加密、签名机制
    SpringBoot-从新建一个项目看起
    linux下vi命令修改文件及保存的使用方法
    集合
    第一个注解式的SpringMVC项目
  • 原文地址:https://www.cnblogs.com/YJZoier/p/9404492.html
Copyright © 2020-2023  润新知