树形dp入门

目录

1.树形dp的定义
2.最大独立集
3. 树上背包
4. 树的最小顶点覆盖
5. 树的最长路径

1.树形dp的定义

树形dp是一种dp思想,将dp建立在树状结构的基础上。它是先算子树然后进行合并,即:先遍历子树,遍历完之后把子树的值合并给父亲。
树形dp的关键方法是dfs。从根节点出发,向子节点做深度优先搜索,并由其子节点的最优解合并得到该节点的最优解。

2.最大独立集

2.1 最大独立集的定义
独立集是指图 G 中两两互不相邻的顶点构成的集合。最大独立集是具有最大尺寸的独立集。
2.2例题
没有上司的舞会
题目链接AcWing算法基础课 285.没有上司的舞会 树形dp入门_第1张图片
树形dp入门_第2张图片

2.3思路
本道题是有向图,由上司指向职员。用动态数组去存储图的边。has_fa[i]表示i是否有父节点,1为有,0为没有。
构造两个状态0,1。f[i][0]表示不选择i点时,i点及其子树能得到的最大快乐指数。f[i][1]表示选择i点时,i点及其子树能得到的最大快乐指数。j为i的儿子节点,happy[i]表示i的幸运值。
f[i][0]=∑(max(f[j][0],f[j][1]))
f[i][1]=happy[i]+∑f[j][0]
结果为max(f[root][0],f[root][1])
2.4代码

//AC代码
#include
using namespace std;
#define int long long
const int N=6010;
int n,happy[N],has_fa[N],f[N][2];
vector<int> e[N];
void dfs(int u)
{
    f[u][1]=happy[u];
    for(int i=0;i<e[u].size();i++)
    {
        int j=e[u][i];
        dfs(j);
        f[u][1]+=f[j][0];
        f[u][0]+=max(f[j][0],f[j][1]);
    }
}
signed main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>happy[i];
    int m=n-1;
    while(m--)
    {
        int a,b;
        cin>>a>>b;
        e[b].push_back(a);
        has_fa[a]=1;
    }
    int root=1;
    while(has_fa[root]) root++;
    dfs(root);
    cout<<max(f[root][1],f[root][0]);
    return 0;
}

3.树上背包

3.1 例题
有依赖的背包问题
题目链接AcWing算法提高课10.有依赖的背包问题
(算法提高课背包问题板块中)
树形dp入门_第3张图片
树形dp入门_第4张图片
3.2有依赖背包的定义
有依赖的背包问题是指物品之间存在依赖关系,这种依赖关系可以用一棵树来表示,要是我们想要选择子节点就必须连同其父节点一块选。
3.3思路
f[x][v]表达选择以x为子树的物品,在容量不超过v时所获得的最大价值。把有依赖的背包问题看成是分组背包问题,每一个结点是看成是分组背包问题中的一个组,子节点的每一种选择我们都看作是组内的一种物品,因此我们可以通过分组背包的思想去写。
3.4代码

//AC代码
#include
using namespace std;
const int N=110;
int n,m,f[N][N],v[N],w[N],root,x,y,z;
vector<int> e[N];
void dfs(int u)
{
    for(int i=0;i<(int)e[u].size();i++)//对当前结点的边进行遍历 
    {
        int son=e[u][i];
        dfs(son);
        for(int j=m-v[u];j>=0;j--){ 
        //遍历背包的容积,因为我们是要遍历其子节点,所以当前节点我们是默认选择的。
        //这个时候当前结点我们看成是分组背包中的一个组,子节点的每一种选择我们都看作是组内一种物品,所以是从大到小遍历。
        //我们每一次都默认选择当前结点,因为到最后根节点是必选的。 
            for(int k=0;k<=j;k++)
                f[u][j]=max(f[u][j],f[u][j-k]+f[son][k]);
        }
    }
    for(int i=m;i>=v[u];i--) f[u][i]=f[u][i-v[u]]+w[u];//加上刚默认选择的父节点价值
    for(int i=0;i<v[u];i++) f[u][i]=0;//去遍历子节点的组合 
}
signed main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>v[i]>>w[i]>>z;
        if(z==-1) root=i;
        else e[z].push_back(i);
    }
    dfs(root);
    cout<<f[root][m];
    return 0;
}

4.最小顶点覆盖

4.1例题
战略游戏
题目链接AcWing算法提高课——战略游戏
树形dp入门_第5张图片
树形dp入门_第6张图片
树形dp入门_第7张图片

4.2思路
虽然是无向图,为了用树形dp,我们将其当作有向图处理,必须从根节点搜索。
f[i][1]以i为根的子树在i上放置的士兵的最少所需的士兵数目
f[i][0]以i为根的子树在i上不放置的士兵的最少所需的士兵数目
f[i][1]=1+∑min(f[j][0],f[j][1])
f[i][0]=∑f[j][j] (j是i的儿子)
结果为min(f[root][0],f[root][1])
4.3代码

#include
using namespace std;
#define int long long
vector<int>e[1510];
int t,n,x,y,st[1510],f[1510][2];
void dfs(int u)
{
    f[u][1]=1,f[u][0]=0;
    for(int i=0;i<(int)e[u].size();i++)
    {
        int j=e[u][i];
        dfs(j);
        f[u][0]+=f[j][1];
        f[u][1]+=min(f[j][1],f[j][0]);
    }
}
signed main()
{
    while(cin>>t)
    {
        memset(st,0,sizeof st);
        for(int i=0;i<t;i++) e[i].clear();
        while(t--)
        {
            cin>>n;
            
            scanf(":(%lld)",&x);
            while(x--)
            {
                cin>>y;
                e[n].push_back(y);
                st[y]=1;
            }
        }   
        int root=0;
        while(st[root]) root++;
        dfs(root);
        cout<<min(f[root][0],f[root][1])<<endl;
    }
    return 0;    
}

5.树的最长路径

5.1 例题
题目链接AcWing算法提高课 ——树的最长路径
树形dp入门_第8张图片

5.2 思路
初始不确定树的拓扑结构,所以要建立双向边
先讨论对于给定拓扑结构的树里的任意节点,经过他的路径:
树形dp入门_第9张图片
观察我标出的红色节点,那么经过他的路径有3种:
1.以其 子树中的某个节点 作为 起点,以他作为 终点 的 粉色路径
2.以其 子树中的某个节点 作为 起点,以子树中的某个节点作为终点的蓝色路径
3.以其 子树中的某个节点 作为 起点,以 非其子树的节点 作为 终点 的 橙色路径
对于第 1 种情况,我们可以直接递归处理其子树,找出到当前子树根节点最长的路径长度即可。
对于第 2 种情况,我们在处理第1 种情况时,顺便找出1类路径的 次长路径把最长和次长拼在一起,就是我们要的第 2 种情况。
而对于第 3 种情况,我们可以把它归类为其 祖先节点 的第 1,2种情况,让其 祖先节点 去处理即可。
d1表示以节点 u为根的子树中,从子树某个节点到 u 的最长路
d2表示以节点 u为根的子树中,从子树某个节点到 u 的次长路
ans为路径的最大长度。
5.3 代码

//AC代码
#include
#define int long long
using namespace std;
const int N=10010;
vector<pair<int,int>>s[N];
int ans,n,m,a,b,c;
int dfs(int u,int father)
{
    int dist=0;
    int d1=0,d2=0;
    for(int i=0;i<(int)s[u].size();i++)
    {
        auto j=s[u][i];
        if(j.first==father) continue;
        int d=dfs(j.first,u)+j.second;
        dist=max(dist,d);
        if(d>d1) d2=d1,d1=d;
        else if(d>d2) d2=d;
    }
    ans=max(ans,d1+d2);
    return dist;
}
signed main()
{
    cin>>n;
    n--;
    while(n--)
    {
        cin>>a>>b>>c;
        s[a].push_back({b,c});
        s[b].push_back({a,c});
    }
    dfs(1,-1);
    cout<<ans;
    return 0;
}

你可能感兴趣的:(ACM每周知识点,深度优先,算法,数据结构)