树形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\n", 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\n", 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\n", i, f[i], n-son[i], son[i]);
chkmn(mn, f[i]);
}
printf("%lld\n", mn);
}
return 0;
}