zoukankan      html  css  js  c++  java
  • 树形dp-二次扫描+换根

    简介

    如果需要求出无根树上每一个点的答案,且一般树形dp只能一次求解(一般)一个点答案时,往往可以考虑二次换根。

    基本思想

    因为树形dp时以指定根为根的树内某节点子树的贡献会被计算,往往考虑使用已计算得到的这些贡献来求出其他点的答案(一般会在第一次计算出被指定为根的点的答案)

    例题

    HDU-2196

    分析

    题目要求求出每个点在树内的最长距离。每个点dfs会导致O((n^2))复杂度。我们设f[i]为指定根树内节点i在其此时子树内能到达的最远距离,g[i]表示i在外部树能达到的最远距离,答案即是(max(f[i],g[i]))。首先我们可以一遍dfs处理出f[i]。处理g[i]时,我们是用i来更新它的子节点的(那i谁来更新i?当然是i的da.i没有da怎么办?n那么它就是指定根,指定根答案已被计算...)。首先找到更新f[i]的节点xb1,用f[xb1] + dis{xb1,i}更新子树内其他所有点的g[];并求出da的次长距离,更新g[xb1],以上求出了在i子树范围内个子节点能到达的最远距离;然后再考虑i外部子树对子节点的贡献--用g[x]更新即可,还要递归处理其他节点。

    #include <iostream> 
    #include <cstdio> 
    #include <cstring> 
    #include <cmath> 
    #include <algorithm> 
    #include <queue> 
    #include <map> 
    #include <set> 
    #include <bitset>
    #define Max(a,b) (a>b?a:b)
    #define Min(a,b) (a<b?a:b)
    #define ll long long
    #define rint register int
    #define mid ((L + R) >> 1)
    #define lson (x << 1)
    #define rson (x << 1 | 1)
    using namespace std;
    template<typename xxx>inline void read(xxx &x) {
    	x = 0;int f = 1;char c = getchar();
    	for(;c ^ '-' && !isdigit(c);c = getchar());
    	if(c == '-') f = -1,c = getchar();
    	for(;isdigit(c);c = getchar()) x = (x << 3) + (x << 1) + (c ^ '0');
    	x *= f;  
    }
    template<typename xxx>inline void print(xxx x) {
    	if(x < 0) {
    		putchar('-');
    		x = -x;
    	}
    	if(x > 9) print(x / 10);
    	putchar(x % 10 + '0');
    }
    const int maxn = 20010;
    const int mod = 1e9 + 7;
    const int inf = 0x7f7f7f7f;
    struct edge{
    	int to,val,last;
    }e[maxn];
    int head[maxn],tot;
    inline void add(int from,int to,int val) {
    	++tot;
    	e[tot].to = to;
    	e[tot].val = val;
    	e[tot].last = head[from];
    	head[from] = tot;
    } 
    int f[maxn];//f[i]表示i在以i为根的子树内的到叶节点的最大距离
    int g[maxn];//g[i]表示i在以i为根的子树外的最远距离 
    inline void ddfs2(int x,int da) {//以x更新x子树的g 
    	int xb1 = 0,v1 = 0;
    	int xb2 = 0,v2 = 0;
    	for(rint i = head[x];i;i = e[i].last) {
    		if(e[i].to ^ da) {
    			if(f[e[i].to] + e[i].val >= f[xb1] + v1) xb1 = e[i].to,v1 = e[i].val;//找f[x]的更新来源 ,也是找x在子树中最长 
    		}
    	}
    	for(rint i = head[x];i;i = e[i].last) {
    		if(e[i].to ^ da && e[i].to ^ xb1) {
    			g[e[i].to] = f[xb1] + v1 + e[i].val;
    			if(f[e[i].to] + e[i].val >= f[xb2] + v2) xb2 = e[i].to,v2 = e[i].val;//找x在子树中次长	
    		}
    	}
    	g[xb1] = f[xb2] + v1 + v2;//次长更新最长,以上处理完x所有子节点在子树内的g 
    	for(rint i = head[x];i; i = e[i].last) {
    		if(e[i].to ^ da) {
    			if(g[e[i].to] < g[x] + e[i].val) g[e[i].to] = g[x] + e[i].val;//更新了子树对g的贡献还应更新x外部树对x子树的贡献 
    			ddfs2(e[i].to,x);
    		}
    	}
    	return ;
    }
    inline void ddfs1(int x,int da) {
    	for(rint i = head[x];i;i = e[i].last) {
    		if(e[i].to ^ da) {
    			ddfs1(e[i].to,x);
    			if(f[x] < f[e[i].to] + e[i].val) f[x] = f[e[i].to] + e[i].val; 
    		}
    	}
    	return ;
    }
    int n;
    int main() {
    	while(~scanf("%d",&n)) {
    		tot = 0;
    		memset(f,0,sizeof(f));
    		memset(g,0,sizeof(g));
    		memset(head,0,sizeof(head));
    		for(rint i = 2;i <= n; ++i) {
    			int a,b;
    			read(a);read(b);
    			add(i,a,b);
    			add(a,i,b);
    		}
    		f[0] = g[0] = -inf;
    		ddfs1(1,0);
    		ddfs2(1,0);//我们以1为根,则1的答案即是为f[1] 
    		for(rint i = 1;i <= n; ++i) print(Max(f[i],g[i])),putchar('
    ');
    	}
    }
    /*
    */
    

    POJ-3585

    分析

    这个数据应该hack了不少题解
    1
    5
    1 2 0
    2 3 3
    2 4 2
    4 5 6
    手摸后从1到n每个点ans该是0 5 2 8 2,
    但是很多题解的程序输出0 5 3 8 6。
    有谁知道吗

    很简单的二次换根,有些题解的错误好像在于

    if (in[x]==1) g[to]=f[to]+e[i].val;
    else g[to]=f[to]+min (e[i].val, g[x]-min (e[i].val, f[to]) );
    

    else 后怎么肯定是g[x]-min (e[i].val, f[to])???,如果in[to] == 1那么e[i].val才是to对x的贡献啊。

    #include <iostream> 
    #include <cstdio> 
    #include <cstring> 
    #include <cmath> 
    #include <algorithm> 
    #include <queue> 
    #include <map> 
    #include <set> 
    #include <bitset>
    #define Max(a,b) (a>b?a:b)
    #define Min(a,b) (a<b?a:b)
    #define ll long long
    #define rint register int
    #define mid ((L + R) >> 1)
    #define lson (x << 1)
    #define rson (x << 1 | 1)
    using namespace std;
    template<typename xxx>inline void read(xxx &x) {
    	x = 0;int f = 1;char c = getchar();
    	for(;c ^ '-' && !isdigit(c);c = getchar());
    	if(c == '-') f = -1,c = getchar();
    	for(;isdigit(c);c = getchar()) x = (x << 3) + (x << 1) + (c ^ '0');
    	x *= f;  
    }
    template<typename xxx>inline void print(xxx x) {
    	if(x < 0) {
    		putchar('-');
    		x = -x;
    	}
    	if(x > 9) print(x / 10);
    	putchar(x % 10 + '0');
    }
    const int maxn = 200010;
    const int mod = 1e9 + 7;
    const int inf = 0x7f7f7f7f;
    struct edge{
    	int to,last;ll val;
    }e[maxn<<1];
    int head[maxn],tot;
    inline void add(int from,int to,ll val) {
    	++tot;
    	e[tot].to = to;
    	e[tot].val = val;
    	e[tot].last = head[from];
    	head[from] = tot;
    } 
    ll dp[maxn],n,ans[maxn];
    int in[maxn];
    inline void ddfs2(int x,int da) {	
    	ll tem;
    	for(rint i = head[x];i;i = e[i].last) {
    		if(e[i].to ^ da) { 
    			if(in[x] == 1) ans[e[i].to] = dp[e[i].to] + e[i].val;
    			else {
    				ans[e[i].to] = dp[e[i].to];
    				if(in[e[i].to] == 1) {
    					ans[e[i].to] += Min(e[i].val,ans[x] - e[i].val);
    				} else {
    					ans[e[i].to] += Min(e[i].val,ans[x] - Min(e[i].val,dp[e[i].to]));
    				}
    			}
    			ddfs2(e[i].to,x);
    		}	
    	}
    	return ;
    } 
    inline void ddfs1(int x,int da) {
    	for(rint i = head[x];i;i = e[i].last) {
    		if(e[i].to ^ da) {
    			ddfs1(e[i].to,x);
    			if(in[e[i].to] == 1) dp[x] += e[i].val;//遇见叶节点加边权
    			else dp[x] += Min(dp[e[i].to],e[i].val);//下面一定,上面管子再粗也没用。上面一定,下面可以的流量再大也没用 
    		}
    	}
    	return ; 
    }
    int main() {
    	int t;read(t);
    	while(t--) {
    		tot = 0;
    		memset(in,0,sizeof(in));
    		memset(dp,0,sizeof(dp));
    		memset(head,0,sizeof(head));
    		read(n);
    		for(rint i = 2;i <= n; ++i) {
    			int a,b,c;
    			read(a);read(b);read(c);
    			add(a,b,c);add(b,a,c);
    			++in[a];++in[b];
    		}
    		ddfs1(1,0);
    		ans[1] = dp[1];
    		ddfs2(1,0);
    		ll maxx = 0;
    		for(rint i = 1;i <= n; ++i) maxx = Max(maxx,ans[i]);
    		print(maxx);putchar('
    ');
    	}
    }
    /*
    */
    
  • 相关阅读:
    Node.js+express 4.x 入门笔记
    排序系列 之 堆排序算法 —— Java实现
    排序系列 之 归并排序算法 —— Java实现
    javascript必须知道的知识要点(二)
    XGBoost安装
    ubuntu16.04查看哪些软件可更新
    禁止ubuntu更新内核
    ubuntu安装nvidia显卡驱动问题
    禁用ubuntu16.04的guest账户
    远程登录jupyter notebook
  • 原文地址:https://www.cnblogs.com/Thomastine/p/11852377.html
Copyright © 2011-2022 走看看