树形行dp

本文探讨了如何在树结构中找到重心,即最远离某节点的两个节点,以及最长路径的问题。通过C++代码实例展示了使用深度优先搜索(DFS)和动态规划的方法来解决这两个问题。涉及的数据结构包括邻接矩阵、邻接表和递归函数的应用。
摘要由CSDN通过智能技术生成

树的重心

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const int M=2*N;//以有向图的格式存储无向图,所以每个节点至多对应2n-2条边
int st[M];
int n;
int h[M],idx,e[M],ne[M]; //链式向前星
int ans=0x3f3f3f;
void add(int a,int b){   //头插法把b插到a的头节点的后面
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}

int dfs(int u){    //递归遍历树,每次递归返回的是以当前节点为根节点的节点数
    st[u]=true;
    int res=0;
    int sum=1;   //当前根节点,sum是以u
    for(int i=h[u];i!=-1;i=ne[i]){//不难发现,每次循环最多只会进行两次,因为树节点最多有两个
    //分叉,每次一个循环求的是左子树或者右子树的节点数
        int j=e[i];
        if(!st[j]){
            int k=dfs(j);
            res=max(res,k);
            sum+=k;
        }
    }
    res=max(res,n-sum);
    ans=min(ans,res);
    return sum;
}
int main(){
    memset(h,-1,sizeof h);
    cin >> n;
    for(int i=0;i<n-1;i++){
        int a,b;
        cin >> a >> b;
        add(a,b),add(b,a);
    }
    int wu=dfs(1);
    cout << ans;
    return 0;
}

参考题解

树的最长路径

//
#include<bits/stdc++.h>
using namespace std;
const int N=2*(1e4+10);
int head[N],e[N],ne[N],idx,w[N];
int n,ans;
void add(int a,int b,int c){//邻接矩阵建图
    w[idx]=c,e[idx]=b,ne[idx]=head[a],head[a]=idx++;
}
int dfs(int u,int father){
    int d1=0,d2=0;//d1表示以u为根往下找的最长路径,d2表示以u为根
    //不同于最长路径的次长路径
    for(int i=head[u];i!=-1;i=ne[i]){//显然循环的次数就是u的临近子节点个数,每次循环求的就是从某个临近子节点出发得到的最长路径,然后在和从其他临近
    //子节点出发得到的最长路径进行比较,注意我们每次都以u为根节点
        int j=e[i];
        if(j==father) continue;//防止找父节点
        int k=dfs(j,u)+w[i];//我们要想更新u的最长路径,就必须要先知道他的子节点的最长路径 ,dp思想,所以要先dfs
        if(k>=d1){//如果得到的新路径是大于从一个路径的话
            d2=d1;
            d1=k;
        }
        else if(k>d2){
            d2=k;
        }
    }
    ans=max(ans,d1+d2);
    return d1;//返回的就是以u为根的距离他最远的那个点的最长路径
}
//每次递归做两件事情,一件是找出以u为根的最长路径即d1,一件是找出经过u的最长
//老用户不过
int main(){
    cin >> n;
    memset(head, -1, sizeof head);//一定不要忘了初始化表头
    for(int i=1;i<=n-1;i++){
        int a,b,c;
        cin >> a >> b >> c;
        add(a,b,c);
        add(b,a,c); //不理解建图的话可以画一个图试试
    }
    dfs(1,-1);
    cout << ans;
    return 0;
}

参考题解

树的中心

每个节点可分为两部分找离它最远的节点,一种是向下找 这和上题树的重心一样,一种是
向上找李它最远的节点,向上找的话就是
up[u]=w[u至父节点]+max(d1[],d2[],up[]),显然d1[]>d2[],但当d1[]经过u时我们只能选择d2[]
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int n;
int h[N],w[N*2],e[N*2],ne[N*2],idx;
int dn1[N],dn2[N],up[N],son1[N],son2[N];
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void dfs_down(int u,int father){
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(j==father) continue;
        dfs_down(j,u);//在这里进行dp要因为求子节点先被算出来
        if(w[i]+dn1[j]>=dn1[u]){
            dn2[u]=dn1[u];
            dn1[u]=w[i]+dn1[j];
            son2[u]=son1[u];
            son1[u]=j;
        }
        else if(w[i]+dn1[j]>dn2[u]){
            dn2[u]=w[i]+dn1[j];
            son2[u]=j;
        }
    }
    return;
}
void dfs_up(int u,int father){
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(j==father) continue;
        if(j==son1[u]){  //如果说d1[u]经过他的子节点j
            up[j]=max(up[u]+w[i],dn2[u]+w[i]);
        }
        else {
            up[j]=max(up[u]+w[i],dn1[u]+w[i]);
        }
        dfs_up(j,u); // 在这里进行dp是因为要求父节点先被算出来
    }
    return;
}
int main(){
    memset(h, -1, sizeof h); //不要忘了邻接表初始化!
    cin >> n;
    for(int i=0;i<n-1;i++){
        int a,b,c;
        cin >> a >> b >> c;
        add(a,b,c),add(b,a,c);
    }
    dfs_down(1,-1);
    dfs_up(1,-1);
    int ans=0x3f3f3f3f;
    for(int i=1;i<=n;i++){
        ans=min(ans,max(up[i],dn1[i]));
    }
    cout << ans;
    return 0;
}

数字转换

#include<bits/stdc++.h>
using namespace std;
const int N=50010;
int ne[N*2],e[N*2],h[N],idx;
int n;
int sum[N];
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int d1[N],d2[N];
int res;
int dfs(int u,int father){
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(j==father) continue;
        int k=dfs(j,u)+1;
        if(k>=d1[u]){
            d2[u]=d1[u];
            d1[u]=k;
        }
        else if(k>d2[u]){
            d2[u]=k;
        }
    }
    res=max(res,d1[u]+d2[u]);
    return d1[u];
}
int main(){
    cin >> n;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++){//这题不是求n的约数啊 肯定不能用 n/i;
        for(int j=2;j*i<=n;j++){
            sum[i*j]+=i; //sum[i]=j 表示i的约数之和为j 不理解代入
        }
    }
    for(int i=1;i<=n;i++){
        if(sum[i]<i){
            add(sum[i],i),add(i,sum[i]);
        }
    }
    dfs(1,-1);
    cout << res;
    return 0;
}

没有上司的舞会

#include<bits/stdc++.h>
using namespace std;
const int N=6010;
int h[N],e[N*2],ne[N*2],w[N],idx;
int n;
bool st[N];
int f[N][2]; //状态机 f[i][0]表示以i为根节点且不选i的最大价值,
//f[i][1]表示以i为根节点且选i的最大价值
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u){
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        dfs(j);
        f[u][1]+=f[j][0];
        f[u][0]+=max(f[j][0],f[j][1]);
    }
}
int main(){
    cin >> n;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++){
        cin >> w[i];
        f[i][1]=w[i];
    }
    for(int i=1;i<=n-1;i++){
        int a,b;
        cin >> a >> b;
        add(b,a);
        st[a]=true;
    }
    int tmp=0;
    for(int i=1;i<=n;i++){
        if(!st[i]){
            dfs(i);
            tmp=i;
        }
    }
  //  cout << f[3][1] << endl;
    cout << max(f[tmp][1],f[tmp][0]);
    return 0;
}

有依赖的背包问题

#include<bits/stdc++.h>
using namespace std;
const int N=110,M=N*2;
int h[N],e[M],ne[M],idx;
int n,m;
int w[N],v[N],root;
int f[110][110];//f[x][v]表达选择以x为子树的物品,在容量不超过v时所获得的最大价值
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u){   
    for(int i=h[u];~i;i=ne[i]){ // 枚举每一组物品
        int a=e[i];
        dfs(a);
        for(int j=m;j>=v[u];j--){ // 必选u节点,给u留出来空位置
            for(int k=0;k<=j-v[u];k++){//给子树的空间不能大于j-v[u],否则无法选择u节点,
            //例如j=m时,则k要<=m-v[u],k如果也取m的话那么就不能选择u节点了
                f[u][j]=max(f[u][j],f[u][j-k]+f[a][k]);//f[u][j-k]和f[a][k]一定没有重复的,
                //因为我们一定是用新的节点没有参与过运算的节点去更新旧节点的
            }
        }
    }
    for(int i=v[u];i<=m;i++){
        f[u][i]+=w[u];
    }
}
int main(){
    cin >> n >> m;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++){
        int father;
        cin >> v[i] >> w[i] >> father;
        if(father==-1){
            root=i;
            continue;
        }
        add(father,i);
    }
    dfs(root);
    cout << f[root][m];
    return 0;
}

二叉苹果树

#include<bits/stdc++.h>
using namespace std;
const int N=110,M=N*2;
int f[N][N];
int n,m;
int h[N],e[M],ne[M],idx;
int w[M];
int root;
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void dfs(int u,int father){
    for(int i=h[u];~i;i=ne[i]){
        int a=e[i];
        if(a==father) continue;
        dfs(a,u);
        for(int j=m;j>=1;j--){
            for(int k=0;k<=j-1;k++){
                f[u][j]=max(f[u][j],f[u][j-k-1]+f[a][k]+w[i]); //注意这里与上提不同,是f[u][j-k-1];
            }
        }
    }
}
int main(){
    memset(h,-1,sizeof h);
    cin >> n >> m;
    for(int i=1;i<=n-1;i++){
        int a,b,c;
        cin >> a >> b >> c;
        add(a,b,c),add(b,a,c);
    }
    dfs(1,-1);
    cout << f[1][m];
    return 0;
}

生命之树

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int h[N],e[N*2],ne[N*2],idx,w[N];
int n;
long long int f[N]; // f[i]表示以i为根节点的连通块最大价值
int root;
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int dfs(int u,int father){
    // int res=0;
    // int flag=1;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(j==father) continue;
        dfs(j,u);
        f[u]+=max(f[j],(long long)0);
    }
}
int main(){
    cin >> n;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++){
        cin >> w[i];
        f[i]=w[i];
    }
    for(int i=1;i<=n-1;i++){
        int a,b;
        cin >> a >> b;
        add(a,b);
        add(b,a);
    }
    long long int res=-0x3f3f3f3f;
    dfs(3,-1);
    for(int i=1;i<=n;i++){
        res=max(f[i],res);
    }
    cout << res;
    return 0;
}

皇宫看守

#include<bits/stdc++.h>
using namespace std;
const int N=1510;
int n;
bool st[N];
int e[N],ne[N],w[N],idx,h[N];
int f[N][3]; //f[][0]表示他的父节点放了守卫且当前节点不放守卫
//,f[][1]表示他的某个儿子节点放了守卫且当前节点不放守卫,f[][2]表示他自己放了守卫 
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u){
    f[u][2]=w[u];
    int sum=0;
    for(int i=h[u]; ~i ; i=ne[i]){
        int j=e[i];
        dfs(j);
        f[u][0]+=min(f[j][2],f[j][1]);
        f[u][2]+=min(f[j][0],min(f[j][1],f[j][2]));
        sum+=min(f[j][1],f[j][2]);
    }
    f[u][1] = 1e9;//因为要求最小值所以初始化为最大值
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        f[u][1]=min(f[u][1],sum-min(f[j][1],f[j][2])+f[j][2]);
    }
}
int main(){
    cin >> n;
    memset(h,-1,sizeof h);
    for (int i = 1; i <= n; i ++ )
    {
        int id, cost, cnt;
        cin >> id >> cost >> cnt;
        w[id] = cost; //因为是点上的权重所以在这里初始化,如果是边上的权重则在add函数里初始化
        while (cnt -- )
        {
            int ver;
            cin >> ver;
            add(id, ver);
            st[ver] = true;
        }
    }
    int root=1;
    while(st[root]) root++;
    dfs(root);
    cout << min(f[root][2],f[root][1]) << endl;
    return 0;
}

参考题解

战略游戏

//类比上司的舞会那道题是每条边至多有一个点   ,本题是一条边至少有一个点,
#include<bits/stdc++.h>
using namespace std;
const int N=1510;
int h[N],e[N],ne[N],idx;
bool st[N];
int f[N][2];
int n;
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u){
    f[u][1]=1,f[u][0]=0;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        dfs(j);
        f[u][0]+=f[j][1];
        f[u][1]+=min(f[j][1],f[j][0]);
    }
}
int main(){
    while(cin >> n ){
        idx=0; //为什么这里写成 int idx=0; 就是wa
        int ver,cnt;
        memset(h,-1,sizeof h);
        memset(st,0,sizeof st);
        for(int i=0;i<n;i++){
            scanf("%d:(%d)",&ver,&cnt);
            while(cnt--){
                    int b;
                    cin >> b;
                    add(ver,b);
                    st[b]=true;
                }
        }
        int root=0;
        while(st[root]) root++;
        dfs(root);
        cout << min(f[root][0],f[root][1])<<endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值