zoukankan      html  css  js  c++  java
  • 树形DP

    poj2342
    题意:子节点和父亲节点不能同时选,问最大价值。
    分析:f[i][0]表示不选节点i,f[i][1]表示选择节点i。
    假设u为父亲,v为儿子,可得f[u][1] += f[v][0], f[u][0] += max(f[v][0], f[v][1])。
    答案为max(f[rt][0], f[rt][1])。

    #include<bits/stdc++.h>
    
    using namespace std;
    
    const int N = 6010;
    
    template <typename T>
    T read(){
    	T n(0), f(1);
    	char ch = getchar();
    	for(; !isdigit(ch); ch=getchar()) if(ch == '-') f = -1;
    	for(; isdigit(ch); ch=getchar()) n = n*10 + ch-48;
    	return n*f;
    }
    
    int n;
    int f[N][2], a[N], fa[N], ind[N], vis[N];
    
    void dfs(int u){
    	vis[u] = 1;
    	for(int i = 1; i <= n; i++){
    		if(!vis[i] && fa[i] == u){
    			dfs(i);
    			f[u][1] += f[i][0];
    			f[u][0] += max(f[i][1], f[i][0]);
    		}
    	}
    }
    
    int main(){
    	n = read<int>();
    	for(int i = 1; i <= n; i++) a[i] = read<int>();
    	for(int i = 1; i < n; i++){
    		int x, y;
    		x = read<int>(), y = read<int>();
    		fa[x] = y;
    		ind[x]++;
    	}
    
    	for(int i = 1; i <= n; i++) f[i][1] = a[i];
    	for(int i = 1; i <= n; i++){
    		if(!ind[i]){
    			dfs(i);
    			printf("%d
    ", max(f[i][0], f[i][1]));
    			break;
    		}
    	}
    
    	return 0;
    }
    

    hdu1520(同上,数据加强版)
    分析:将dfs内循环1~n改为只找当前节点的子树。

    #include<bits/stdc++.h>
    
    using namespace std;
    
    #define pb push_back
    
    const int N = 6010;
    
    template <typename T>
    T read(){
    	T n(0), f(1);
    	char ch = getchar();
    	for(; !isdigit(ch); ch=getchar()) if(ch == '-') f = -1;
    	for(; isdigit(ch); ch=getchar()) n = n*10 + ch-48;
    	return n*f;
    }
    
    int n;
    int f[N][2], a[N], fa[N], ind[N], vis[N];
    vector<int> s[N];
    
    void dfs(int u){
    	vis[u] = 1;
    	for(int j = 0; j < s[u].size(); j++){
    		int i = s[u][j];
    		if(!vis[i]){
    			dfs(i);
    			f[u][1] += f[i][0];
    			f[u][0] += max(f[i][1], f[i][0]);
    		}
    	}
    }
    
    int main(){
    	n = read<int>();
    	for(int i = 1; i <= n; i++) a[i] = read<int>();
    	for(int i = 1; i < n; i++){
    		int x, y;
    		x = read<int>(), y = read<int>();
    		s[y].pb(x);
    		ind[x]++;
    	}
    
    	for(int i = 1; i <= n; i++) f[i][1] = a[i];
    	for(int i = 1; i <= n; i++){
    		if(!ind[i]){
    			dfs(i);
    			printf("%d
    ", max(f[i][0], f[i][1]));
    			break;
    		}
    	}
    
    	return 0;
    }
    

    hdu2196
    题意:求树上每个点能到达的最远距离
    分析:两次dfs。
    f[i][0]表示走i的子树的最长距离,f[i][1]表示走i的子树的次长距离(与f[i][0]是不同的儿子),f[i][2]表示往上经过i的父亲的最长距离。
    第一次从下往上,处理好f[i][0]和f[i][1]。
    当f[v][0]+w[i] > f[u][0]时 :f[u][1] = f[u][0]; f[u][0] = f[v][0] + w[i];
    否则当f[v][0]+w[i] > f[u][1]时 :f[u][1] = f[v][0] + w[i];
    第二次从上往下更新子节点的f[v][2]。
    如果u的最长边是经过v的(f[u][0] == f[v][0] + w[i]),那么通过u只能去u的次长边的树或者u往上父亲的树。//不知道题目保不保证最长边只有一条...?
    否则如果v不属于u的最长边,那么通过u只能去u的最长边的树或者u的父亲的树。

    #include<bits/stdc++.h>
    
    using namespace std;
    
    const int N = 10010;
    
    template <typename T>
    T read(){
    	T n(0), f(1);
    	char ch = getchar();
    	for(; !isdigit(ch); ch=getchar()) if(ch == '-') f = -1;
    	for(; isdigit(ch); ch=getchar()) n = n*10 + ch-48;
    	return n*f;
    }
    
    int n, e;
    int f[N][3], begin[N];
    struct node{
    	int to, nxt, w;
    }E[N<<1];
    
    void add(int x, int y, int z){
    	E[++e].to = y; E[e].w = z; E[e].nxt = begin[x];
    	begin[x] = e;
    }
    
    void dfs(int u, int fa = 0){
    	for(int i = begin[u]; i; i = E[i].nxt){
    		int v = E[i].to;
    		if(v == fa) continue;
    		dfs(v, u);	
    		if(f[v][0] + E[i].w > f[u][0]){
    			f[u][1] = f[u][0];
    			f[u][0] = f[v][0] + E[i].w;
    		}
    		else if(f[v][0] + E[i].w > f[u][1]) f[u][1] = f[v][0] + E[i].w;
    	} 
    }
    
    void dfs_(int u, int fa = 0){
    	for(int i = begin[u]; i; i = E[i].nxt){
    		int v = E[i].to;
    		if(v == fa) continue;
    		if(f[v][0] + E[i].w == f[u][0]){
    			f[v][2] = max(f[u][2], f[u][1])+E[i].w;
    		}
    		else f[v][2] = max(f[u][2], f[u][0])+E[i].w;
    		dfs_(v, u);
    	}
    }
    
    int main(){
    	n = read<int>();
    	for(int i = 2; i <= n; i++){
    		int x, y;
    		x = read<int>(); y = read<int>();
    		add(x, i, y);
    	}
    
    	dfs(1);
    	dfs_(1);
    	for(int i = 1; i <= n; i++) printf("%d ", max(f[i][0], f[i][2]));
    
    	return 0;
    }
    

    poj3107
    题意:求树的重心
    分析:(感觉不算是树形DP了,直接dfs回溯的时候计数)
    son[i]表示i的子树个数,f[i]表示除去i节点后剩余分支子树最大值。
    答案即为min(f[i]), 按照字典序输出。
    假设v是u的儿子,
    f[u] = max(n-son[u], max(son[v]));
    //n-son[u]表示u以上的节点个数,我们将u视作根节点时,u以上即为u的一棵子树。然后与u的其他子树中的(节点树)最大子树选max

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    
    using namespace std;
    
    const int oo = 0x7f7f7f7f;
    const int N = 50010;
    
    template <typename T>
    T read(){
    	T n(0), f(1);
    	char ch = getchar();
    	for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
    	for(; isdigit(ch); ch = getchar()) n = n*10 + ch-48;
    	return n*f;
    }
    
    int n, e, mn;
    int to[N<<1], nxt[N<<1];
    int son[N], begin[N], smx[N], f[N];
    
    void add(int x, int y){
    	to[++e] = y; nxt[e] = begin[x]; begin[x] = e;
    	to[++e] = x; nxt[e] = begin[y]; begin[y] = e;
    }
    
    void chkmn(int& a, int b){
    	a = a < b ? a : b;
    }
    
    void dfs(int u, int fa = 0){
    	for(int i = begin[u]; i; i = nxt[i]){
    		int v = to[i];
    		if(v == fa) continue;
    		dfs(v, u);
    		son[u] += son[v];
    		if(son[v] > smx[u]) smx[u] = son[v];
    	}
    	f[u] = max(n-son[u], smx[u]);
    }
    
    int main(){
    	n = read<int>();
    	for(int i = 1; i < n; ++i){
    		int x, y;
    		x = read<int>();
    		y = read<int>();
    		add(x, y);
    	}
    
    	for(int i = 1; i <= n; ++i) son[i] = 1, smx[i] = 0;
    	dfs(1);
    	mn = oo;
    	for(int i = 1; i <= n; ++i) chkmn(mn, f[i]);
    	for(int i = 1; i <= n; ++i) if(f[i] == mn) printf("%d ", i);
    	puts("");
    	return 0;
    }
    

    poj3140
    题意:删除某边后剩余两个分支差值最小是多少
    分析:把上面的程序瞎**改一下,不过这题每个节点都有一个值,所以总数不是n而是sum(总值)。
    //我真的都没思考,随便改一改试一试WA了x次之后竟然对了...

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    
    using namespace std;
    
    typedef long long ll;
    const ll oo = 1e18+7;
    const int N = 100010;
    
    template <typename T>
    T read(){
    	T n(0), f(1);
    	char ch = getchar();
    	for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
    	for(; isdigit(ch); ch = getchar()) n = n*10 + ch-48;
    	return n*f;
    }
    
    int n, m, e;
    ll mn, sum;
    ll to[N<<1], nxt[N<<1];
    ll son[N], begin[N], f[N];
    
    void init(){
    	sum = 0;
    	for(int i = 1; i <= m<<1; ++i) son[i] = nxt[i] = 0;
    	for(int i = 1; i <= n; ++i) begin[i] = f[i] = 0;
    }
    
    void add(int x, int y){
    	to[++e] = y; nxt[e] = begin[x]; begin[x] = e;
    	to[++e] = x; nxt[e] = begin[y]; begin[y] = e;
    }
    
    void chkmn(ll& a, ll b){
    	a = a < b ? a : b;
    }
    
    void dfs(int u, int fa = 0){
    	for(int i = begin[u]; i; i = nxt[i]){
    		int v = to[i];
    		if(v == fa) continue;
    		dfs(v, u);
    		son[u] += son[v];
    	}
    	f[u] = sum-son[u] > son[u] ? sum-son[u]*2 : 2*son[u]-sum;
    }
    
    int main(){
    	int tot = 0;
    	while(scanf("%d%d", &n, &m) && n && m){
    		if(!n && !m) break;
    
    		init();
    		for(int i = 1; i <= n; i++) son[i] = read<int>(), sum += son[i];
    		for(int i = 1; i <= m; ++i){
    			int x, y;
    			x = read<int>();
    			y = read<int>();
    			add(x, y);
    		}
    		
    		printf("Case %d: ", ++tot);
    		//for(int i = 1; i <= n; ++i) son[i] = 1;
    		dfs(1);
    		mn = oo;
    		for(int i = 1; i <= n; ++i){
    			//printf("%d %lld %lld %lld
    ", i, f[i], n-son[i], son[i]);
    			chkmn(mn, f[i]);
    		}
    		printf("%lld
    ", mn);
    	}
    	return 0;
    }
    
  • 相关阅读:
    java 笔记(2) 接口作为引用数据类型
    linux 笔记(5)让vi或vim显示行数和不显示行数
    linux 笔记(4)Ubuntu 使用时vi编辑器时,不能使用backspace键来进行退格或者不能正常使用
    linux 笔记(3)sudo passwd 设置root用户的密码
    matlab笔记(1) 元胞结构cell2mat和num2cell
    linux 笔记(2) 目录直接强行删除rm -rf *(删除当前目录所有的内容)
    linux 笔记(1) ctrl+c,ctrl+z,ctrl+d
    C51单片机项目:红绿灯
    C51单片机项目:时钟
    java 笔记(1)在子类中,一定要访问父类的有参构造方法?
  • 原文地址:https://www.cnblogs.com/hanser/p/7729255.html
Copyright © 2011-2022 走看看