树形DP题目分类

1、树的重心
找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心。删去重心后,生成的多棵树尽可能平衡。
对于一棵n个节点的无根树,找到一个点,使得树变成以该点为根的有根树时,最大子树的结点数最小。换句话说,删除这个点后最大连通块(一定是树)的结点数最小。
2、树的直径

poj 1655 Balancing Act

题意:求树的重心以及删除重心后最大子树的节点数,如果有多个重心,只输出编号最小的点。
从1开始搜索,把它作为整个树的根(哪个点作为树根对答案是没有影响的,不妨设树根编号为1,从1开始搜索)。dp[i]表示以 i 为根的最大子树的大小(包括 i 自身)。
注意dfs函数中多次递归,dfs函数内的一些变量不要设成全局变量,否则会错。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=2e4+10;
int n,t,x,y,cnt,ans1,ans2,dp[N],head[N];
struct edge
{
    int to,next;
}e[N<<1];//无向树 边*2
void add(int x,int y)//链式前向星存边
{
    e[cnt].to=y;
    e[cnt].next=head[x];
    head[x]=cnt++;
}
void dfs(int u,int father)//u为当前节点,father为u的父节点
{
    dp[u]=1;//u自身就是一个子树,大小至少为1
    int mx1=0,mx2=0;//mx1,mx2不能写成全局变量!
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int v=e[i].to;//v为u的子节点(v不能写成全局变量!)
        if(v==father)continue;//只找u的子节点,跳过父节点
        dfs(v,u);//向下方递归
        dp[u]=dp[u]+dp[v];//递归返回上层,向上方回溯,记录dp值
        mx1=max(mx1,dp[v]);//找以v为根的最大子树,或者说是u下方(不包括u)的最大子树
    }
    mx2=max(mx1,n-dp[u]);//u的上方还有子树,它的大小是n-dp[u],可能它会比u下方的最大子树要大
    if(mx2<ans2)
    {
        ans1=u;
        ans2=mx2;
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>t;
    while(t--)
    {
        cin>>n;
        memset(head,-1,sizeof(head));
        cnt=0;ans2=0x3f3f3f3f;
        for(int i=1;i<n;i++)
        {
            cin>>x>>y;
            add(x,y);
            add(y,x);
        }
        dfs(1,0);//从1开始搜索,设1的父节点为0
        printf("%d %d\n",ans1,ans2);
    }
    return 0;
}

poj 3107 Godfather

题意:求树的重心,如果有多个重心,按升序全部输出。
把上题代码稍加改动,用 ans1[i] 表示以 i 为根的最大子树的节点数(不包括根本身),ans2表示以1~n为根的所有的最大子树的节点数(不包括根本身)。

还有就是这题在poj的玄学评测下,如果算法正确,但是输入写的是ios::sync_with_stdio(false)优化的cin,会超时(2000ms+),改成scanf输入就AC了...我寻思着也就poj的玄学评测姬能做到cin输入优化还超时,这问题在其他OJ我是真没遇到过...

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=5e4+10;
int n,t,x,y,cnt,ans1[N],ans2,dp[N],head[N];
struct edge
{
    int to,next;
}e[N<<1];
void add(int x,int y)
{
    e[cnt].to=y;
    e[cnt].next=head[x];
    head[x]=cnt++;
}
void dfs(int u,int father)
{
    dp[u]=1;
    int mx=0;//mx不能写成全局变量!
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int v=e[i].to;//v不能写成全局变量!
        if(v==father)continue;
        dfs(v,u);
        dp[u]+=dp[v];
        mx=max(mx,dp[v]);
    }
    mx=max(mx,n-dp[u]);
    if(mx<=ans2)
    {
        ans1[u]=mx;
        ans2=mx;//ans1[u]表示以u为根的最大子树的节点数(不包括根本身),ans2是以1~n为根的所有的最大子树的节点数(不包括根本身)
     }      
}
int main()
{
    scanf("%d",&n);
    memset(head,-1,sizeof(head));
    ans2=0x3f3f3f3f;
    for(int i=1;i<n;i++)
    {
        scanf("%d %d",&x,&y);
        add(x,y);
        add(y,x);
    }
    dfs(1,0);
    for(int i=1;i<=n;i++)
        if(ans1[i]==ans2)printf("%d ",i);
    return 0;
}

hdu 1520 Anniversary party

题意:在树中,每个节点有一个权值,相邻的父节点和子节点中最多只能选择一个,问如何选择使得权值和最大。

还有就是注意本题是多组输入(单组就WA了)

#include <bits/stdc++.h>
using namespace std;
const int N=6010;
int n,x,y,s,cnt,val[N],vis[N],head[N],dp[N][2];//dp[i][0]表示不取i点时的最优解,dp[i][1]表示取i点时的最优解
struct edge
{
    int to,next;
}e[N<<1];
void add(int x,int y)
{
    e[cnt].to=y;
    e[cnt].next=head[x];
    head[x]=cnt++;
}
void dfs(int u,int father)
{
    dp[u][0]=0;//不取u点,dp值为0
    dp[u][1]=val[u];//取u点,dp值为val[u]
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int v=e[i].to;
        if(v==father)continue;
        dfs(v,u);
        dp[u][0]+=max(dp[v][1],dp[v][0]);//不取u点时,v点可取(dp[v][1])可不取(dp[v][0]),dp[u][0]加上两者的最大值
        dp[u][1]+=dp[v][0];//取u点时,v点不可取,dp[u][1]加上dp[v][0]
    }
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n)
    {
        cnt=0;
        memset(vis,0,sizeof(vis));
        memset(head,-1,sizeof(head));
        for(int i=1;i<=n;i++)
            cin>>val[i];//记录点的权值
        while(cin>>x>>y&&!(x==0&&y==0))//x是y的子节点
        {
            add(x,y);
            add(y,x);
            vis[x]=1;//标记作为子节点的x
        }
        for(int i=1;i<=n;i++)
        {
            if(vis[i]==0)
            {s=i;break;}//找树根s,它没有被标记为其他任何点的子节点
        }
        dfs(s,0);//从树根s开始向下搜索
        printf("%d\n",max(dp[s][0],dp[s][1]));
    }
    return 0;
}

nefu 248 聚会的快乐

和上题差不多。注意一下用map将string映射到int的过程。

#include <bits/stdc++.h>
using namespace std;
const int N=110;
string sx,sy;
map<string,int>vis;
map<string,int>id;
int n,x,y,z,s,num,cnt,is_son[N],val[N],head[N],dp[N][2];
struct edge
{
    int to,next;
}e[N<<1];
void add(int x,int y)
{
    e[cnt].to=y;
    e[cnt].next=head[x];
    head[x]=cnt++;
}
void dfs(int u,int father)
{
    dp[u][0]=0;
    dp[u][1]=val[u];
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int v=e[i].to;
        if(v==father)continue;
        dfs(v,u);
        dp[u][0]+=max(dp[v][0],dp[v][1]);
        dp[u][1]+=dp[v][0];
    }
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n)
    {
        memset(head,-1,sizeof(head));
        memset(is_son,0,sizeof(is_son));
        memset(val,0,sizeof(val));
        num=0;cnt=0;
        vis.clear();
        for(int i=1;i<=n;i++)
        {
            cin>>sx>>z>>sy;
            vis[sx]++;//vis记录字符串出现的次数
            vis[sy]++;
            if(vis[sx]==1)id[sx]=++num;//num为现在的编号,id[]记录字符串对应的编号
            x=id[sx];
            if(vis[sy]==1)id[sy]=++num;
            y=id[sy];
            val[x]=z;
            is_son[x]=1;
            add(x,y);
            add(y,x);
        }
        for(int i=1;i<=num;i++)
        {
            if(is_son[i]==0)
            {s=i;break;}//s为树根
        }
        dfs(s,0);
        printf("%d\n",max(dp[s][0],dp[s][1]));
    }
    return 0;
}

hdu 1561 The more, The Better

树上背包。

#include <bits/stdc++.h>
using namespace std;
const int N=210;
int n,m,y,z,cnt,val[N],head[N],dp[N][N];
struct edge
{
    int to,next;
}e[N<<1];
void add(int x,int y)
{
    e[cnt].to=y;
    e[cnt].next=head[x];
    head[x]=cnt++;
}
void dfs(int u,int father)
{
    dp[u][1]=val[u];
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int v=e[i].to;
        if(v==father)continue;
        dfs(v,u);
        for(int j=m;j>1;j--) // 背包
            for(int k=0;k<j;k++)
            dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>m&&!(n==0&&m==0))
    {
        cnt=0;
        memset(dp,0,sizeof(dp));
        memset(head,-1,sizeof(head));
        for(int i=1;i<=n;i++)
        {
            cin>>y>>z;
            add(i,y);
            add(y,i);
            val[i]=z;
        }
        m++;
        dfs(0,-1);
        printf("%d\n",dp[0][m]);
    }
    return 0;
}

hdu 2196 Computer

最长链。

#include <bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int n,y,z,cnt,dp[N][3],head[N];
struct edge
{
    int w,to,next;
}e[N<<1];
void add(int x,int y,int z)
{
    e[cnt].to=y;
    e[cnt].w=z;
    e[cnt].next=head[x];
    head[x]=cnt++;
}
void dfs1(int u,int father)
{
    int one=0,two=0;
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int v=e[i].to;
        if(v==father)continue;
        dfs1(v,u);
        int tmp=dp[v][0]+e[i].w;
        if(tmp>=one)
        {
            two=one;
            one=tmp;
        }
        else if(tmp>two&&tmp<one)
            two=tmp;
    }
    dp[u][0]=one;
    dp[u][1]=two;
}
void dfs2(int u,int father)
{
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int v=e[i].to;
        if(v==father)continue;
        if(dp[v][0]+e[i].w==dp[u][0])//u的最长距离在v及其下方
            dp[v][2]=max(dp[u][2],dp[u][1])+e[i].w;
        else//u的最长距离不在v及其下方
            dp[v][2]=max(dp[u][2],dp[u][0])+e[i].w;
        dfs2(v,u);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n)
    {
        cnt=0;
        memset(head,-1,sizeof(head));
        for(int i=2;i<=n;i++)
        {
            cin>>y>>z;
            add(i,y,z);
            add(y,i,z);
        }
        dfs1(1,0);
        dfs2(1,0);
        for(int i=1;i<=n;i++)
            printf("%d\n",max(dp[i][0],dp[i][2]));
    }
    return 0;
}