hdu5834 Magic boy Bi Luo with his excited tree

hdu5834 Magic boy Bi Luo with his excited tree

  • 题意:一棵树,每个点有点权,边有边权,从一点出发,遍历树,每次遍历到一条边就减去边权,第一次遍历到一个点就加上点权(后续不加)。问从所有点出发能得到最大价值。

  • 刚碰这题认为这题和洛谷的Kamp很像,都是点权一次(点权可以设置为0),边权多次。但是洛谷的kamp是给定了遍历到哪些点,而这题是所有点都可以被遍历到。这也就导致了,Kamp解题思路中的边权值*2加上最长边的“最长边”这一个信息很难在这题中被维护。

  • 所以,我们需要换一个思路。

  • 提示: u u u表示当前结点, v v v表示 u u u的儿子结点。

  • 我们设 d p [ u ] [ 0 / 1 / 2 ] dp[u][0/1/2] dp[u][0/1/2]分别代表**回到点 u u u**的最大价值,不回到点 u u u的最大价值不回到点 u u u的次大价值。其中不回答 u u u的最大价值次大价值其实就是我们加上的最长边所对应的 v v v不一样(为何要维护次大价值后续再说)

  • 小tip: d p [ u ] [ 1 ] − d p [ u ] [ 0 ] = m a x l e n dp[u][1] - dp[u][0]=maxlen dp[u][1]dp[u][0]=maxlen,其中 m a x l e n maxlen maxlen就是上面说的根节点到遍历的所有点中的最长边。换句话说其实 d p [ u ] [ 1 ] dp[u][1] dp[u][1]也可以由 m a x l e n + d p [ u ] [ 0 ] maxlen+dp[u][0] maxlen+dp[u][0]得到。再换句话说,对于 d p [ u ] [ 1 ] dp[u][1] dp[u][1]其实就是遍历的所有点中的多条边中,除了最长边减了1次,其他都减了两次。

    • 所以 d p [ u ] [ 0 ] dp[u][0] dp[u][0]的很好递推,就是 d p [ u ] [ 0 ] + = m a x ( 0 , d p [ v ] [ 0 ] − 2 ∗ e d g e [ i ] . w ) dp[u][0]+=max(0, dp[v][0] - 2 * edge[i].w) dp[u][0]+=max(0,dp[v][0]2edge[i].w)。这里取 m a x max max是因为对于这个点可取可不取。
    • d p [ u ] [ 1 ] dp[u][1] dp[u][1]就需要用到 d p [ u ] [ 0 ] dp[u][0] dp[u][0]了。同理 d p [ u ] [ 2 ] dp[u][2] dp[u][2]这个次大值也需要实时更新。
    • d p [ u ] [ 1 ] dp[u][1] dp[u][1]需要讨论,对于一个儿子 v v v如果 2 ∗ e d g e [ i ] . w − d p [ v ] [ 0 ] < = 0 2 * edge[i].w-dp[v][0]<=0 2edge[i].wdp[v][0]<=0表示 d p [ u ] [ 0 ] dp[u][0] dp[u][0]没有取过这个点。但是对于 d p [ u ] [ 1 ] dp[u][1] dp[u][1]他是可能取到这个点的。我们看小tip中的加粗字体,可以知道虽然我减 v v v这个儿子所对应的边两次不合适,但是可以只减一次。即这个点所对应的边可能是上述所说的最长边。 t m p = d p [ u ] [ 0 ] − e d g e [ i ] . w + d p [ v ] [ 1 ] ; tmp = dp[u][0] - edge[i].w + dp[v][1]; tmp=dp[u][0]edge[i].w+dp[v][1];就是表示只取了一次。
    • 如果 2 ∗ e d g e [ i ] . w − d p [ v ] [ 0 ] > 0 2 * edge[i].w-dp[v][0]>0 2edge[i].wdp[v][0]>0表示 d p [ u ] [ 0 ] dp[u][0] dp[u][0]取过这个边。同理,取过的话我们也要看看这个边只减去一次是不是最优的 d p [ u ] [ 1 ] dp[u][1] dp[u][1]即这个点对应的边可能也是上述说的最长边。 t m p = d p [ u ] [ 0 ] + e d g e [ i ] . w + d p [ v ] [ 1 ] − d p [ v ] [ 0 ] ; tmp = dp[u][0] + edge[i].w + dp[v][1] - dp[v][0]; tmp=dp[u][0]+edge[i].w+dp[v][1]dp[v][0];这个方程整体含义是因为 d p [ u ] [ 0 ] dp[u][0] dp[u][0]取过这个点,所以我们要先减去这个点对 d p [ u ] [ 0 ] dp[u][0] dp[u][0]的影响,然后加上只取一次的影响。
  • dfs2

    • 我们要理清思路。一个点不回到 u u u的最大价值无非就是最长边在子树的,或者最长边经过父节点的。而最长边在子树的,我们dfs1已经得到了,所以dfs2是用来得到最长边在父节点的价值。
    • 但是最长边在父节点我们就遇到了问题,我们发现我们通过父节点更新这个 v v v并不简单。
    • 假设父节点的最长边经过的点 m a x s o n [ u ] [ 1 ] maxson[u][1] maxson[u][1]不是我们现在遍历的 v v v。我们可以通过父节点的 d p [ u ] [ 1 ] dp[u][1] dp[u][1]再减去 v v v d p [ u ] [ 1 ] dp[u][1] dp[u][1]的贡献,然后统计三个最大值,一个是最长边经过父节点 u u u的,一个是最长边在儿子的并且通过父节点取到其他点的,一个是最长边在儿子,但是不同过父节点取到其他点的。之后就是维护细节。
    • 但是如果 m a x s o n [ u ] [ 1 ] = = v maxson[u][1]==v maxson[u][1]==v的话,我们发现我们要统计的两个最大值中的最长边经过父节点 u u u的很难得到。所以我们需要为何次优值。来实现这个。
  • 代码:

  #include <bits/stdc++.h>
    using namespace std;
    
    const int MAXN = 1e5 + 10, M = 2e5 + 10;
    const int INF = 0x3f3f3f3f;
    typedef pair<int, int>pii;
    typedef long long ll;
    int head[MAXN], cnt = 0, val[MAXN];
    int maxson[MAXN][3];
    int dp[MAXN][3];
    struct Edge{
    	int to, next, w;
    }edge[MAXN * 2];
    inline void addedge(int u, int v, int w) {
        edge[cnt] = {v, head[u], w};head[u] = cnt++;
        edge[cnt] = {u, head[v], w};head[v] = cnt++;
    }
    
    void dfs1(int u, int fa){
    	dp[u][0] = dp[u][1] = dp[u][2] = val[u];
    	for(int i = head[u]; ~i; i = edge[i].next){
    		int v = edge[i].to;
    		if(v == fa)continue;
    		dfs1(v, u);
    		dp[u][1] = dp[u][2] = dp[u][0] = dp[u][0] + max(0, dp[v][0] - 2 * edge[i].w);
    	}
    	for(int i = head[u]; ~i; i = edge[i].next){
    		int v = edge[i].to;
    		if(v == fa)continue;
    		int tmp;
    		if(dp[v][0] - 2 * edge[i].w <= 0)tmp = dp[u][0] - edge[i].w + dp[v][1];
    		else tmp = dp[u][0] + edge[i].w + dp[v][1] - dp[v][0];
    		if(tmp > dp[u][1]){
    			dp[u][2] = dp[u][1];
    			maxson[u][2] = maxson[u][1];
    			dp[u][1] = tmp;
    			maxson[u][1] = v;
    		}else if(tmp > dp[u][2]){
    			dp[u][2] = tmp;
    			maxson[u][2] = v;
    		}
    	}
    }
    void dfs2(int u, int fa){
    	for(int i = head[u]; ~i; i = edge[i].next){
    		int v = edge[i].to;
    		if(v == fa)continue;
    		int tmp;
    		if(v == maxson[u][1])tmp = dp[u][2];
    		else tmp = dp[u][1];
    		if(dp[v][0] - 2 * edge[i].w > 0){
    			if(tmp + edge[i].w >= max(0, dp[u][0] - dp[v][0]) + dp[v][1]){
    				dp[v][2] = max(0, dp[u][0] - dp[v][0]) + dp[v][1];maxson[v][2] = maxson[v][1];
    				dp[v][1] = tmp + edge[i].w; maxson[v][1] = u;
    			}else{
    				dp[v][1] = max(0, dp[u][0] - dp[v][0]) + dp[v][1];
    				dp[v][2] = max(0, dp[u][0] - dp[v][0]) + dp[v][2];
    				if(tmp + edge[i].w > dp[v][2]){
    					dp[v][2] = tmp + edge[i].w; maxson[v][2] = u;
    				}
    			}
    			dp[v][0] = max(0, dp[u][0] - dp[v][0]) + dp[v][0];
    		}else{
    			if(tmp - edge[i].w + dp[v][0] >=  max(dp[u][0] - 2 * edge[i].w, 0) + dp[v][1]){
    				dp[v][2] = max(dp[u][0] - 2 * edge[i].w, 0) + dp[v][1];maxson[v][2] = maxson[v][1];
    				dp[v][1] = tmp - edge[i].w + dp[v][0]; maxson[v][1] = u;
    			}else{
    				dp[v][1] = max(dp[u][0] - 2 * edge[i].w, 0) + dp[v][1];
    				dp[v][2] = max(dp[u][0] - 2 * edge[i].w, 0) + dp[v][2];
    				if(tmp - edge[i].w + dp[v][0] > dp[v][2]){
    					dp[v][2] = tmp - edge[i].w + dp[v][0]; maxson[v][2] = u;
    				}
    			}
    			dp[v][0] = max(0, dp[u][0] - 2 * edge[i].w) + dp[v][0];
    		}
    		dfs2(v, u);
    	}
    }
    int main() {
        int t;
        scanf("%d", &t);
        int tt = 0;
        while(t--){
        	int n;
        	cin >> n;
        	for(int i = 1; i <= n; i++){
        		head[i] = -1;
        		maxson[i][0] = maxson[i][1] = 0;
        		dp[i][0] = dp[i][1] =dp[i][2] = 0;
        	}
        	cnt = 0;
        	for(int i = 1; i <= n; i++)cin >> val[i];
        	for(int i = 1; i <= n - 1; i++){
        		int u, v, w;
        		scanf("%d%d%d", &u, &v, &w);
        		addedge(u, v, w);
        	}
        	dfs1(1, 0);
        	dfs2(1, 0);
        	printf("Case #%d:\n", ++tt);
        	for(int i = 1; i <= n; i++){
        		printf("%d\n", dp[i][1]);
        	}
        }
        return 0;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值