# 树形DP总结

Problem Description

There is going to be a party to celebrate the 80-th Anniversary of the Ural State University. The University has a hierarchical structure of employees. It means that the supervisor relation forms a tree rooted at the rector V. E. Tretyakov. In order to make the party funny for every one, the rector does not want both an employee and his or her immediate supervisor to be present. The personnel office has evaluated conviviality of each employee, so everyone has some number (rating) attached to him or her. Your task is to make a list of guests with the maximal possible sum of guests' conviviality ratings.

Input

Employees are numbered from 1 to N. A first line of input contains a number N. 1 <= N <= 6 000. Each of the subsequent N lines contains the conviviality rating of the corresponding employee. Conviviality rating is an integer number in a range from -128 to 127. After that go T lines that describe a supervisor relation tree. Each line of the tree specification has the form:
L K
It means that the K-th employee is an immediate supervisor of the L-th employee. Input is ended with the line
0 0

Output

Output should contain the maximal sum of guests' ratings.

Sample Input

7 1 1 1 1 1 1 1 1 3 2 3 6 4 7 4 4 5 3 5 0 0

Sample Output

5

dp【i】【0】表示当前i这个人不选，dp【i】【1】表示当前i这个人安排去参加。

而  dp【st】【0】+=max（dp【i】【0】，dp【i】【1】）  而在树中 st 是 i 的父节点。

Dfs+dp（超时）

1. #include <iostream>

2. #include <cstdio>

3. #include <cstdlib>

4. #include <cstring>

5. #include <algorithm>

6. #include <queue>

7. #include <vector>

8. using namespace std;

9. #define Del(a,b) memset(a,b,sizeof(a))

10. const int N = 10000;

11. int dp[N][3];  ///dp[i][0]表示当前i点不选 1表示选

12. int father[N],vis[N];

13. int n;

14. void creat(int o)

15. {

16.     vis[o]=1;

17.     for(int i=1;i<=n;i++)  //超时原因，每次都循环n次，//时间复杂度on^2

18.     {

19.         if(vis[i]==0 && father[i]==o)

20.         {

21.             creat(i);

22.             dp[o][0]+=max(dp[i][0],dp[i][1]);

23.             dp[o][1]+=dp[i][0];

24.         }

25.     }

26. }

27.

28. int main()

29. {

30.     int i;

31.

32.     while(~scanf("%d",&n))

33.     {

34.         Del(dp,0);Del(father,0);

35.         Del(vis,0);

36.         for(i=1; i<=n; i++)

37.         {

38.             scanf("%d",&dp[i][1]);

39.         }

40.         int f,c,root;

41.         root = 0;//记录父结点

42.         bool beg = 1;

43.         while (scanf("%d %d",&c,&f),c||f)

44.         {

45.             father[c] = f;

46.             if( root == c || beg )

47.             {

48.                 root = f;

49.             }

50.         }

51.         while(father[root])//查找父结点

52.             root=father[root];

53.         creat(root);

54.         int imax=max(dp[root][0],dp[root][1]);

55.         printf("%d\n",imax);

56.     }

57.     return 0;

58.

59. }

1STLvector实现链表

#include<stdio.h>

#include<string.h>

#include<iostream>

#include<vector>

#include<algorithm>using namespace std;

const int MAXN=6050;

vector<int>vec[MAXN];int f[MAXN];int hap[MAXN];

int dp[MAXN][2];

void dfs(int root)

{

int len=vec[root].size();

dp[root][1]=hap[root];

for(int i=0;i<len;i++)

dfs(vec[root][i]);//记录了每个节点的子节点，时间复杂度/////onlogn

for(int i=0;i<len;i++)

{

dp[root][0]+=max(dp[vec[root][i]][1],dp[vec[root][i]][0]);

dp[root][1]+=dp[vec[root][i]][0];

}

}int main()

{

//freopen("in.txt","r",stdin);

//freopen("out.txt","w",stdout);

int n;

int a,b;

while(scanf("%d",&n)!=EOF)

{

for(int i=1;i<=n;i++)

{

scanf("%d",&hap[i]);

vec[i].clear();

f[i]=-1;//树根标记

dp[i][0]=dp[i][1]=0;

}

while(scanf("%d%d",&a,&b))

{

if(a==0&&b==0)break;

f[a]=b;

vec[b].push_back(a);

}

a=1;

while(f[a]!=-1)a=f[a];//找到树根

dfs(a);

printf("%d\n",max(dp[a][1],dp[a][0]));

}

return 0;

}

#include<stdio.h>

#include<string.h>

#include<algorithm>using namespace std;

const int MAXN=6010;

struct Node

{

int v;

Node *next;

};

Node edge[MAXN*2];//这个要大一点

int tol;//边的总数，也就是edge数组

int dp[MAXN][2];

int hap[MAXN];

bool vis[MAXN];

void init()

{

tol=0;

memset(dp,0,sizeof(dp));

memset(vis,false,sizeof(vis));

}

edge[tol].v=b;

edge[tol].v=a;

}

void dfs(int v)

{

if(vis[v])return;

vis[v]=true;

dp[v][1]=hap[v];

while(p!=NULL)

{

if(!vis[p->v])

{

dfs(p->v);

dp[v][0]+=max(dp[p->v][0],dp[p->v][1]);

dp[v][1]+=dp[p->v][0];

}

p=p->next;

}

}

int main()

{

// freopen("in.txt","r",stdin);

// freopen("out.txt","w",stdout);

int n,a,b;

while(scanf("%d",&n)!=EOF)

{

init();

for(int i=1;i<=n;i++)

scanf("%d",&hap[i]);

while(scanf("%d%d",&a,&b))

{

if(a==0&&b==0)break;

}

//由于建的是无向图，可以任意找个点当树根进行DP

//但是在搜索中要判重，加个vis数组

dfs(1);

printf("%d\n",max(dp[1][0],dp[1][1]));

}

return 0;

}

状态转移方程:  dp[i] = max(dp[i],tree[i][j].sum)ji的子节点，这个tree[i][j].len是分支的权值和)

2  //两个点
1 2//第二个点直接连到第一个点，边的权值为2

3
1 2
1 3

5
1 1
2 1
3 1
1 1

//用结构体指针构造链表

#include<iostream>

#include<cstring>

#include<cstdio>

#include<algorithm>

#include<cmath>

#define N 10005

using namespace std;

struct node{

int v,len;

long long sum;

node* next;

bool vis[N];

long long dp[N];

int total;

void init(){

total=0;

memset(dp,0,sizeof(dp));

memset(tree,NULL,sizeof(tree));

memset(vis,0,sizeof(vis));

}

tree[total].v=i;

tree[total].len=b;

tree[total].v=a;

tree[total].len=b;

}

void dfs(int v){

//if(vis[v])return ;

vis[v]=1;

while(p!=NULL){

if(!vis[p->v]){

dfs(p->v);

dp[v]=max(dp[v],dp[p->v]+p->len);

p->sum=dp[p->v]+p->len;

}

p=p->next;

}

}

void tree_dfs(int pa,int son){

if(vis[son])return;

vis[son]=1;

long long mmax=0;

while(p!=NULL){

if(p->v!=son){

mmax=max(mmax,p->sum);

}

p=p->next;

}

while(p!=NULL){

if(p->v==pa){

p->sum=mmax+p->len;

break;

}

p=p->next;

}

while(p!=NULL){

tree_dfs(son,p->v);

dp[son]=max(dp[son],p->sum);

p=p->next;

}

}

int main(){

int n;

while(cin>>n&&n!=EOF){

init();

int a,b;

for(int i=2;i<=n;i++){//2开始

cin>>a>>b;

}

dfs(1);

memset(vis,0,sizeof(vis));

while(p!=NULL){

tree_dfs(1,p->v);

p=p->next;

}

for(int i=1;i<=n;i++)

cout<<dp[i]<<endl;

}

return 0;

}

Tips

1，在函数dfs中，dp[v]p->sum结果相同函数dfs的目的：1、算出第一个点到另一点的最大权值；2、算出每个点到其子孙节点的最大权值（因为dfs是有方向的）。

2，函数tree_dfs中，用p->sum做运算，因为dp最终表达的为结果，p->sum为可变的。

3，将数据弄成无向边，因为每个点都要做tree_dfs

1，结构体建立链表的方法：

Int total=0;

Struct node{

Int n;//放数据的

Node *next;//放下一个点的

Edge[total].n=a;

Edge[total].n=b;

}

2,循环的套路：

While(p!=NULL){

If(dp/dfs条件){

Dfsp->v）；

}

P=p->next;

}

Pre：树的分治

NlogN的复杂度利用树型dp可以很好地求树的重心.

#include<iostream>

#include<cstring>

#include<cstdio>

#include<algorithm>

#define N 20005

#define INF 1<<30

using namespace std;

bool vis[N];

int ans,tot,size,n;

int dp[N];

struct node{

int v;

node *next;

void init(){

tot=0;

//ans=INF;

size=INF;

memset(dp,0,sizeof(dp));

memset(vis,0,sizeof(vis));

}

edge[tot].v=b;

}

void dfs(int v){

vis[v]=1;

int tmp=0;///每次dfs都要更新

while(p!=NULL){

if(!vis[p->v]){

dfs(p->v);

dp[v]+=dp[p->v]+1;///记录根的最大子树点数

tmp=max(dp[p->v]+1,tmp);///记录每个节点的最大子树的点数

}

p=p->next;

}

tmp=max(tmp,n-dp[v]-1);

if(tmp<size||(tmp==size&&v<ans)){///tmp相同时，哪个接近根，哪个是重心

size=tmp;

ans=v;

}

}

int main(){

int t;

cin>>t;

while(t--){

init();

cin>>n;

int a,b;

for(int i=2;i<=n;i++){

cin>>a>>b;

}

dfs(1);

cout<<ans<<" "<<size<<endl;

}

return 0;

}

dp[v]+=dp[p->v]+1;

5 4

1 2 3

1 3 1

1 4 2

3 5 1

0 0

///终于懂了QAQ

#include<iostream>

#include<cstring>

#include<cstdio>

#include<algorithm>

#include<cmath>

#define N 21000

#define INF 2147483647

using namespace std;

struct node{

int v,len;

int sum,mark;

node *next;

int dp[N],dis[N];

int size[N],pos[N];

bool vis[N];

int tot,cnt,k,n,ans,root;

void init(){

cnt=ans=0;

for (int i = 0; i < N; ++i)

}

edge[cnt].v=b;

edge[cnt].len=c;

}

void dfs(int v){///计算出各个点与子孙节点的权值

tmp[v].sum=0;tmp[v].mark=0;

while(p!=NULL){

if(!vis[p->v]&&p->v!=v){///第二个条件为了防止只有一个点时的误操作

dfs(p->v);

tmp[v].sum+=tmp[p->v].sum;

tmp[v].mark=max(tmp[v].mark,tmp[p->v].sum);

}

p=p->next;

}

tmp[v].sum++;

pos[tot]=v;

size[tot++]=tmp[v].mark;

}

int getroot(int v){

tot=0;

dfs(v);

int i,mmax,ssum=INF;

int b=tmp[v].mark;

for(i=0;i<tot;i++){

size[i]=max(size[i],b-size[i]);///与以v为根的另外子树点数比较,不要漏下！

if(ssum>size[i]){

mmax=pos[i];

ssum=size[i];

}

}

return mmax;

}

////////////////////////////////上面是找重心的

void getlength(int v,int d){///求每个点到当前根节点的距离

dis[tot++]=d;

while(p!=NULL){

if(!vis[p->v]&&(d+p->len<=k)&&p->v!=v)

getlength(p->v,d+p->len);

p=p->next;

}

}

void count1(int v){

sort(dis,dis+tot);

int left=0,right=tot-1;

while(left<right){

if(dis[left]+dis[right]<=k){

ans+=right-left;

left++;

}else

right--;

}

}

void count2(int v){///ans+=count1-count2

vis[v]=1;

while(p!=NULL){

if(!vis[p->v]){

tot=0;

getlength(p->v,p->len);

sort(dis,dis+tot);

int left=0,right=tot-1;

while(left<right){

if(dis[left]+dis[right]<=k){

ans-=right-left;

left++;

}else

right--;

}

}

p=p->next;

}

}

void solve(int v){///在此分治，找每个子树的重心

root=getroot(v);

tot=0;

getlength(root,0);

count1(root);

count2(root);

while(p!=NULL){

if(!vis[p->v]&&v!=p->v){

solve(p->v);

}

p=p->next;

}

}

int main(){

while(scanf("%d%d", &n, &k),n + k){

init();

for(int i=1;i<n;i++){

int a,b,c;

scanf("%d%d%d",&a,&b,&c);

}

solve(1);

printf("%d\n",ans);

}

return 0;

}////////////////////////////////////////////////////////////为何RE;黑人问号.jpg

1，用dfs算出每个节点到当前根的权值，第i点到根的距离记作dis[i]，所要求的就是dis[i]+dis[j]<k的值。但如果ij两点在同一子树下，就会重复计算，而且不合逻辑（i到根的距离j到根的距离和小于k，按判断条件正确，但这并不是ij的距离小于k），所以在以后的递归中要将多余的删去。

2，有了dis后，在判断是否满足条件时，如果用两个for循环会超时，所以，用到了二分法来判断，只有一个循环（涨姿势了）。

3，用到了树的分治，如果直接dfs会超时，所以要用到分治，在漆子超的论文中有详细证明。

tips

1，两组数组做加减等条件判断时，运用二分找，省时。

2，目前只了解了分治的想法与其作用，但具体问题完全懵B，今后再补。

for(i=m--------1)

for(j=0--------i)

条件+方程；