树形dp小结——1

来源:互联网 发布:unity3d像素游戏制作 编辑:程序博客网 时间:2024/05/23 19:44

1.通常的动态规划都是线性的,或者说是在有向无环图上进行的。而树形dp就是在树的基础上进行的dp

2.树形dp通常有两种方向,一种是自下而上,另一种是自上而下。具体利用方法根据实际要求来

叶——根,在回溯的时候从叶子节点往上更新信息

根——叶,往往是在叶——根dfs一遍以后(相当于预处理),再重新往下获取,得到的最终答案。

3.和普通dp一样,树形dp的重点在于找状态转移方程。


一.入门题

上司的舞会上司的舞会

题目大意:

如题目所言,就是自己的上司参加的话,自己就不能参加。每个节点定义一个价值,子节点和父亲节点不能同时选取。求能够选取的最大价值

题目分析:令f【u】【0】表示不选取u节点,u子树所能得到的最大价值;f【u】【1】表示选取了u节点,u子树能得到的最大价值。

在回溯的过程中通过子节点向上更新信息

设i号节点的价值为a【i】,<u,v>是一条有向边

f【u】【0】=max(f【v】【0】,f【v】【1】)

f【u】【1】=a【u】+f【v】【0】u取了,v肯定不取

void dfs(int x){    int l=tu[x].size();    for(int i=0;i<l;i++)    {        int t=tu[x][i];        dfs(t);        dp[x][0]+=max(dp[t][1],dp[t][0]);        dp[x][1]+=dp[t][0];    }}dfs(root);cout<<max(dp[root][0],dp[root][1])<<endl;

具体代码:

#include <iostream>#include <stdio.h>#include <stdlib.h>#include<string.h>#include<algorithm>#include<math.h>#include<queue>using namespace std;typedef long long ll;const int N=6010;int m,n;int dp[N][2],rd[N];vector <int>tu[N];void dfs(int x){    int l=tu[x].size();    for(int i=0;i<l;i++)    {        int t=tu[x][i];        dfs(t);        dp[x][0]+=max(dp[t][1],dp[t][0]);        dp[x][1]+=dp[t][0];    }}int main(){        while(~scanf("%d",&n))       {           for(int i=1;i<=n;i++)        tu[i].clear();        memset(rd,0,sizeof(rd));       for(int i=1;i<=n;i++)       {           scanf("%d",&dp[i][1]);           dp[i][0]=0;       }       int x,y;       while(~scanf("%d%d",&x,&y)&&x+y)           tu[y].push_back(x),rd[x]++;        int ans=-1;        for(int i=1;i<=n;i++)            if(rd[i]==0)///防止不止一个根节点            {dfs(i);ans=max(ans,max(dp[i][0],dp[i][1]));}        cout<<ans<<endl;       }}


入门题二:

树的重心

题目:点击打开链接

题目大意:

若树上一个节点满足其所有子树中最大子树节点数最少,则这个点即为重心

题目分析:

一个节点有多支是儿子节点,而父亲节点只有一支,题目的意思很清晰,就是在所有儿子子树中寻求一个最大的,然后跟父亲节点的一支进行比较,实际上难在理解,此题将父亲节点的那支也定义成了节点u的子树之一了。而父亲节点的那一支就用总数n减去u的子树所有的节点个数。

任选一点为根,只要统计出每个点的子树大小,就能很快求出每个点子树节点的数量最大值

求每个点子树的大小同样只需要自底向上的更新信息。记size【u】为子树节点u节点的数量(包括u本身)

size【u】=1+(size【v】的总和)

void dfs(int father,int u,int n){    int v,m,temp;    for(int i=next[u];i!=-1;i=edge[i].next)    {        v=edge[i].v;        if(v==father)            continue;        dfs(u,v,n);//先dfs再加        sum[u]+=sum[v];    }      dp[u]=n-1-sum[u];    for(int i=next[u];i!=-1;i=edge[i].next)    {        v=edge[i].v;        if(v==father)            continue;        dp[u]=max(dp[u],sum[v]);    }    sum[u]++;}

代码:

#include<iostream>#include<string.h>#include<algorithm>#define MAX 22000using namespace std;struct edge{    int v;    int next;}edge[MAX*2];int next[MAX];int sum[MAX];int dp[MAX];int ans,n,num;void add_edge(int u,int v){    edge[num].next=next[u];    next[u]=num;    edge[num++].v=v;}void dfs(int father,int u,int n){    int v,m,temp;    for(int i=next[u];i!=-1;i=edge[i].next)    {        v=edge[i].v;        if(v==father)            continue;        dfs(u,v,n);        sum[u]+=sum[v];    }  //  cout<<u<<": "<<sum[u]<<endl;    dp[u]=n-1-sum[u];    for(int i=next[u];i!=-1;i=edge[i].next)    {        v=edge[i].v;        if(v==father)            continue;        dp[u]=max(dp[u],sum[v]);    }    sum[u]++;}int main(){    int t;    int p,q;    cin>>t;    while(t--)    {        cin>>n;        num=0;        memset(sum,0,sizeof(sum));        memset(next,-1,sizeof(next));        for(int i=0;i<n-1;i++)        {            cin>>p>>q;            add_edge(p,q);            add_edge(q,p);        }        ans=1<<29;        p=0;        dfs(0,1,n);        for(int i=1;i<=n;i++)        {            if(dp[i]<ans)            {                ans=dp[i];                p=i;            }        }        cout<<p<<" "<<ans<<endl;    }    return 0;}


入门题3:树上最远距离

这道题最好需要画图理解。

题目大意:如题目所述,一棵无向树,辺有权值,求树上每个点能到达的最远距离

题目分析:

一个点能够走最远距离,第一步要么往儿子方向走,要么找父亲节点。

如果往儿子结点走,就会一直往儿子结点走,不存在返回的现象(以后会遇到又返回的那种题)

如果第一步往父亲节点走,那么第二步要么继续往他的父亲节点走,要么走u的兄弟子树(注意是兄弟,因为不可能回来)兄弟中的最大距离就是次最大距离的含义

%%%往儿子方向走的最远距离是很容易求出来的,而往父亲节点走的就需要自上往下推。这道题与之前两道题是不一样的,就牵扯到了从根向下传递信息。

&&&记f【u】为节点u第一步想儿子方向走的最远距离

&&&记g【u】为节点u第一步向父亲方向走的最远距离

&&&记p【u】为节点u的父亲节点的编号

&&&f【u】=max{f【v】+w(u,v)}

&&&g【u】=w(u,p【u】,max{g【p【u】】,f【v】+w(p【u】,u)})v是u的兄弟

&&&所以要两边dfs,先求出f再求出g

&&&最后节点u的最远距离为  max{f【u】,g【u】}

代码:

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;#define MAXN 10200struct edge{    int to;//终端点    int next;//下一条同样起点的边号    int w;//权值}bians[MAXN*2];int tot;//总边数int head[MAXN];//head[u]=i表示以u为起点的所有边中的第一条边是 i号边void add_bian(int u,int v,int w)//添加从u->v,权值为w的边{    bians[tot].to=v;    bians[tot].w=w;    bians[tot].next = head[u];    head[u] = tot++;}int dist[MAXN][3];//dist[i][0,1,2]分别为正向最大距离,正向次大距离,反向最大距离int jilu[MAXN];int dfs1(int u,int fa)//返回u的正向最大距离{    if(dist[u][0]>=0)        return dist[u][0];    dist[u][0]=dist[u][1]=dist[u][2]=jilu[u]=0;    for(int e=head[u]; e!=-1; e=bians[e].next)    {        int v= bians[e].to;        if(v==fa)            continue;        if(dist[u][0]<dfs1(v,u)+bians[e].w)        {            jilu[u]=v;            dist[u][1] = max(dist[u][1] , dist[u][0]);            dist[u][0]=dfs1(v,u)+bians[e].w;        }        else if( dist[u][1]< dfs1(v,u)+bians[e].w )            dist[u][1] = max(dist[u][1] , dfs1(v,u)+bians[e].w);    }    return dist[u][0];}void dfs2(int u,int fa){    for(int e=head[u];e!=-1;e=bians[e].next)    {        int v = bians[e].to;        if(v==fa)            continue;        if(v==jilu[u])//以u为根节点的经过下一叶子的标号为v        dist[v][2] = max(dist[u][2],dist[u][1])+bians[e].w;//如果是这样的话,那么就找down次最大值与up最大值的最大值        else        dist[v][2] = max(dist[u][2],dist[u][0])+bians[e].w;//如果没有前边的限制,则说明还没到最后,就找down与up哪种最大        dfs2(v,u);    }}int main(){    int n;    while(scanf("%d",&n)==1&&n)    {        tot=0;        memset(dist,-1,sizeof(dist));        memset(head,-1,sizeof(head));        memset(jilu,-1,sizeof(jilu));        for(int i=2; i<=n; i++)        {            int v,w;            scanf("%d%d",&v,&w);            add_bian(i,v,w);//加边            add_bian(v,i,w);//加边        }        dfs1(1,-1);        dfs2(1,-1);        for(int i=1;i<=n;i++)            printf("%d\n",max(dist[i][0],dist[i][2]));    }    return 0;}











原创粉丝点击