树形dp

树是一种特别特别优美的结构

在树上做dp更是一种锦上添花的效果,虽然不知道是谁想出的操作,但是不得不佩服人家的脑力。但是,相对的说,树形dp对于刚接触就有一些措手不及:刚刚才从线性dp过来,树形dp就相当于一记闷棍,重重的敲打在我们的头上。高手勿谈
image

树形dp的关键

说到树形dp,存储首先是一项及其严肃的事情

许多刚接触树形dp的选手,上来就是一通使用链式前向星,邻接链表(很少用,TLE),邻接表等操作直接储存,以至于许多不熟悉图论的选手直接一脸mobile,(可能单纯只是对于我来说)
所以我觉得有必要说明一下怎样储存树形结构
1.邻接矩阵
Linus之父说过:"talk is cheap, show me the code"
我也会贯彻这一做法!(偷懒)
在代码中我做了相关的解释,我相信大家可以看的明白

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int weight[N][N];//weight[i][j]表示从节点i到节点j的权值为weight[i][j]
int visited[N];//防止重复访问
int n;//表示邻接矩阵的行列数
void dfs(int u){
    visited[u]=1;
    for(int i=1;i<=n;i++){
        if(weight[u][i]){
            //相关操作
            cout<<u<<"->"<<i<<" "<<weight[u][i]<<endl;
            if(visited[i])  continue;
            dfs(i);
        }
    }
}

int main(){
    int m;//n个节点和m条边
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int a,b,c;
        cin>>a>>b>>c;//表示节点a和节点b
        weight[a][b]=c;//c表示权值
    }
    dfs(1);
    system("pause");
    return 0;
}
2.边表
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int M=100;
const int N=100;
struct edge{
    int u,v,weight;//u->v的权值为weight
}edge[M];
int n,m;//节点数和边数
int visited[N];//防止循环访问
void dfs(int u){
    visited[u]=true;
    for(int i=1;i<=m;i++){
        if(edge[i].u==u){
            int v=edge[i].v;
            int weight=edge[i].weight;
            //处理
            cout<<u<<"->"<<v<<" "<<weight<<endl;
            if(visited[v])  continue;
            dfs(v);
        }
    }
}

int main()
{

    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int a,b,c;
        cin>>a>>b>>c;
        // edge[i].u=a;
        // edge[i].v=b;
        // edge[i].weight=c;
        edge[i]={a,b,c};
        //如果是双向边
        //edge[i]={b,a,c};
    }
    dfs(1);
    system("pause");
    return 0;
}
3.邻接表
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;//节点的数目和边的数目
const int N=100;
struct edge{
    int v;//终点
    int weight;//权值
};
vector<edge> e[N];
void dfs(int u,int father){
    for(auto ed:e[u]){
        int v=ed.v;
        int weight=ed.weight;
        if(v==father)   continue;//如果终点等于父节点,不用dfs,作用相当于visited数组
        
        cout<<u<<"->"<<v<<" "<<weight<<endl;
        dfs(v,u);
    }
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int a,b,c;
        cin>>a>>b>>c;
        e[a].push_back({b,c});
        e[b].push_back({a,c});
    }
    dfs(1,0);
    system("pause");
    return 0;
}
4.链式邻接表 其实和邻接表差不多
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=100;
struct edge{
    int u,v,weight;//起点和终点和权值
};
vector<edge> e;//边集
vector<int> head[N];//起点u的所有出边

void add(int a,int b,int c){
    e.push_back({a,b,c});
    head[a].push_back(e.size()-1);
}


void dfs(int u,int father){
    for(int i=0;i<head[u].size();i++){
        int j=head[u][i];//出边编号
        int v=e[j].v;
        int weight=e[j].weight;
        if(v==father)  continue;//终点为father,不会dfs
        cout<<u<<"->"<<v<<" "<<weight<<endl;
        dfs(v,u);
    }
}

int main()
{
    int n,m;//节点数目和边数目
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    dfs(1,0);
    system("pause");
    return 0;
}
5。链式前向星
点击查看代码
#include<bits/stdc++.h>
using namespace std;
struct edge{
    int v,weight,next;//终点,权值和下一条边
};
const int N=100,M=100;
edge e[M];
int index=0;
int head[N];//节点的第一条出边
void add(int a,int b,int c){
    e[index]={b,c,head[a]};
    head[a]=index++;
}

void dfs(int u,int father){
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].v;
        int weight=e[i].weight;
        if(v==father) continue;
        cout<<u<<"->"<<v<<" "<<weight<<endl;
        dfs(v,u);
    }
}

int main()
{
    int n,m;
    cin>>n>>m;
    memset(head,-1,sizeof head);
    for(int i=1;i<=m;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);

    }
    dfs(1,0);
    system("pause");
    return 0;
}
相信大家看了上面的代码应该有所领悟。

例题

https://www.luogu.com.cn/problem/P2015
对于这道二叉苹果树来说简直就是树形dp的快乐题(Maybe)
直接上AC代码:

点击查看代码
/*



*/


/*
对题目的理解:
树形dp

*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;

//读入优化
inline ll read()
{
    ll a=0;
    int f=1;    
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-'){
            f=-1;
            ch=getchar();
        }
    }
    while(isdigit(ch)){
        a=(a<<3)+(a<<1)+ch-'0';
        ch=getchar();
    }
    return a*f;
}

struct edge{//链式前向星去保存树
    int v;//终点
    int weight;//权值,在本题中相当于树枝上面的苹果数
    int next;
};

const int maxn=105;
edge e[maxn<<1];
int index=0;
int head[maxn];

void add(int a,int b,int c){
    e[index]={b,c,head[a]};
    head[a]=index++;
}

int n,m;
int sz[maxn];
int dp[maxn][maxn];

void dfs(int u,int father){
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].v;
        if(v==father)   continue;
        dfs(v,u);
        sz[u]+=sz[v]+1;
        for(int j=min(sz[u],m);j;j--){
            for(int k=min(j-1,sz[v]);k>=0;k--){
                dp[u][j]=max(dp[u][j],dp[u][j-k-1]+dp[v][k]+e[i].weight);
            }
        }
    }
}
int main(){
	memset(head,-1,sizeof head);
    n=read();
    m=read();
    for(int i=1;i<n;i++){
        int u=read(),v=read(),w=read();
        add(u,v,w);
        add(v,u,w);
    }
    dfs(1,0);
    cout<<dp[1][m]<<endl;
    system("pause");
	return 0;
}

下面这道题也是一道充满快乐的题。
没有上司的舞会
就给大家留下练手了。网上也该有很多相关的习题,请大家慢慢品鉴!
后序我也会给大家更新一些我对数论的一些想法,敬请期待!

posted @ 2022-11-15 11:15  chattingJhon  阅读(60)  评论(1)    收藏  举报