加载中...

树形dp 围绕某个点为根节点

树形dp 选不选择根节点的问题

没有上司的舞会

对于根节点的子集 都有选择或者不选择的两种情况

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 6010;

int n;
int h[N], e[N], ne[N], idx;
int happy[N];
int f[N][2];
//f[u][1]  i表示以i为根节点而且包含了i的总快乐指数
//f[u][0] i表示以i为根节点并且不含i的总快乐指数
bool has_fa[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs(int u)//搜索 从根节点 往下找 然后return 回来
{
    f[u][1] = happy[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()
{
    scanf("%d", &n);

    for (int i = 1; i <= n; i ++ ) scanf("%d", &happy[i]);

    memset(h, -1, sizeof h);//memset 所有头节点指向-1
    for (int i = 0; i < n - 1; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(b, a);//在图里是 b指向a的 在树里就是根节点指向他
        has_fa[a] = true;//有父亲
    }

    int root = 1;
    while (has_fa[root]) root ++ ;//root相当于 idx 只要这个点有父亲说明不是根节点 那么就向后找

    dfs(root);//找到了就搜

    printf("%d\n", max(f[root][0], f[root][1]));//输出根节点选择或不选择的最大值

    return 0;
}


满二叉树的等长路径https://www.acwing.com/solution/content/99455/

给我们一颗满二叉树每条边有长度,现在想要让根到每个叶子节点的长度相同,我们可以随意的对一条边增加长度

问我们最少增加多少长度,可以使根到每个叶子节点的长度相同.
解:
不对u连着的两条边操作时,u到两个子树中的叶节点的路径长度分别是

x:左子树到叶节点的长度+左子树到u的长度,y:右子树到叶节点的长度+右子树到u的长度
我们现在只要让这两个长度相等即可,所以对答案的贡献是abs(x-y)

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 2050;

int n;
int w[N];//w[i]表示i的父节点到i的距离
int ans;

int dfs(int u)//返回的是每个点到达叶子的距离
{
    if (u * 2 > (1 << n + 1) - 1) return 0;

    int l = dfs(u * 2) + w[u * 2];//左儿子到叶子节点的 加上左儿子的距离
    int r = dfs(u * 2 + 1) + w[u * 2 + 1];
    ans += abs(l - r);
    return max(l, r);//返回最大的距离 (只能增大) 
}

int main()
{
    cin >> n;
    for (int i = 2; i <= (1 << n + 1) - 1; i ++ ) cin >> w[i];
    dfs(1);

    cout << ans << endl;
    return 0;
}



生命之树

查看每个连通块的最大值

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;
typedef long long LL;
const int N = 1e5+10;
LL f[N];
int g[N];
vector<int>e[N];

#define pb push_back

void dfs(int u,int fa){
    f[u]=g[u];
    for (int j : e[u] ){

        if(j!=fa){
        dfs(j,u);//查看每个连通块
        
        f[u]+=max(0ll,f[j]);//
        }
        
    }
}

int main()
{
    int n;
    cin >> n;

    for (int i = 1; i <= n; i ++ ) cin >> g[i];
    for (int i = 1; i <= n-1; i ++ ){
        int a,b;cin>>a>>b;
        e[a].pb(b);
        e[b].pb(a);
    }
    dfs(1,-1);
    LL res=f[1];
    for (int i = 2; i <= n; i ++ ){
        res=max(res,f[i]);
    }
    cout << res;
    return 0;
    
}

gk的树
给出边和 点最大度数k 要求删减一些边 让每个边的边达到最小

思路:对于都超过k的相邻点一定删去她们中间的点最优
正着删除 需要排序各种问题
反着来可以 : 删除所有应该删除的边 在+上对此删除的在两个超k个度数的边的边

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int h[N],ne[N*2],e[N*2],idx;
void add(int a,int b){
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
    
}
int d[N];
int n,k;
int res=0;
void dfs(int u,int fa){
    for(int i=h[u]; ~i; i=ne[i]){
            int j=e[i];
        if(j==fa) continue ;
        dfs(j ,u );
        if(d[j]>k&&d[u]>k){
            res--;//发现删重复就补充上
            d[j]--;
            d[u]--;
        }
    }
}
signed main(){
    int t;cin>>t;
    while(t--){
        memset(h ,-1,sizeof h);
        memset(d, 0,sizeof d);
        idx=0,res=0;
        cin>>n>>k;
        for(int i=0;i<n-1;i++){
            int a,b;cin>>a>>b;
            add(a,b),add(b,a);
            d[a]++,d[b]++;
            
        }
        for(int i=1;i<=n;i++){
            res+=max( 0, d[i]-k);//只要超过就删除
            
        }
        
        dfs(1,-1);
        
        cout<<res<<endl;
        
        
    }
    return 0;
}

旅游规划

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 200010, M = N * 2;

int n;
int h[N], e[M], ne[M], idx;
int d1[N], d2[N], p1[N], up[N];
int maxd;

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs_d(int u, int father)
{
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j != father)
        {
            dfs_d(j, u);
            int distance = d1[j] + 1;
            if (distance > d1[u])
            {
                d2[u] = d1[u], d1[u] = distance;
                p1[u] = j;//p1记录每个节点的最长路径的 子节点
            }
            else if (distance > d2[u]) d2[u] = distance;
        }
    }

    maxd = max(maxd, d1[u] + d2[u]);
}

void dfs_u(int u, int father)
{
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j != father)
        {
            //up[j]的值 肯能有3种情况 up[u]+1,d1[u]+1,d2[u]+1 的最大值 
            up[j] = up[u] + 1;//记录要走的这个j节点的up[j]为他的父节点的最长路径
            if (p1[u] == j) up[j] = max(up[j], d2[u] + 1);//发现这个点要走的子节点 恰好是最长的节点 
            else up[j] = max(up[j], d1[u] + 1);

            dfs_u(j, u);
        }
    }
}

int main()
{
    scanf("%d", &n);
    memset(h, -1, sizeof h);
    for (int i = 0; i < n - 1; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b), add(b, a);
    }

    dfs_d(0, -1);
    dfs_u(0, -1);

    for (int i = 0; i < n; i ++ )
    {
        int d[3] = {d1[i], d2[i], up[i]};
        sort(d, d + 3);
        if (d[1] + d[2] == maxd) printf("%d\n", i);
    }

    return 0;
}

树的中心https://www.acwing.com/problem/content/1075/

找到一个点到树中其他节点的最远距离最近
需要向下d1[] d2[] 以及向上找 up[]

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 10010, M = N * 2, INF = 0x3f3f3f3f;

int n;
int h[N], e[M], w[M], ne[M], idx;
int d1[N], d2[N], p1[N], up[N];
bool is_leaf[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int dfs_d(int u, int father)
{
    d1[u] = d2[u] = -INF;//有可能有0的边?
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (j == father) continue;
        int d = dfs_d(j, u) + w[i];//模板 d为u到j点的距离
        if (d >= d1[u])
        {
            d2[u] = d1[u], d1[u] = d;
            p1[u] = j;
        }
        else if (d > d2[u]) d2[u] = d;
    }

    if (d1[u] == -INF)//如果d1 没有被更新过 说明该点是叶子结点
    {
        d1[u] = d2[u] = 0;
        is_leaf[u] = true;
    }

    return d1[u];
}

void dfs_u(int u, int father)
{
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (j == father) continue;//从父看每一个子结点

        if (p1[u] == j) up[j] = max(up[u], d2[u]) + w[i];//父节点最长的路径上的点就是这个子节点 设置up[j]为max
        else up[j] = max(up[u], d1[u]) + w[i];

        dfs_u(j, u);
    }
}

int main()
{
    cin >> n;
    memset(h, -1, sizeof h);
    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_d(1, -1);
    dfs_u(1, -1);

    int res = d1[1];
    for (int i = 2; i <= n; i ++ )
        if (is_leaf[i]) res = min(res, up[i]);//如果是叶子结点 那么结果是父亲
        else res = min(res, max(d1[i], up[i]));//如果不是叶子结点 结果是上部的

    printf("%d\n", res);

    return 0;
}


有依赖的背包问题

i j i为某个点为根的节点 j表示以某个根节点为体积的
分组背包:
for(每一组i)
for( 每个体积j)
for(每组某个物品k){
if(v[i][k]<=j)
}
}
这里每一组代表的是子树 k代表的是分配的体积

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110;

int n, m;
int v[N], w[N];
int h[N], e[N], ne[N], idx;
int f[N][N];

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 son = e[i];
        dfs(e[i]);//一直往下搜直到边

        // 分组背包
        for (int j = m - v[u]; j >= 0; j -- )  // 循环体积 这个体积最大从m-v[u]开始 因为需要剩余出给v[u]的体积 下面01背包优化了
            for (int k = 0; k <= j; k ++ )  // 循环决策选择第几个东西 实际上是枚举的体积 表示给子树剩下多少体积
                f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]);//等于以son为根节点第k个子树 加上 以u为根节点缺少了k体积的那部分的和为子树的和 f[son][k] k表示分给子节点子树的体积
    }

    // 上面已经找到最大值了 现在将物品u的价值加进去 这里也可以在上面放
    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;//上面的那个for循环利用了这个值 所以需要重新设置为0
}

int main()
{
    cin >> n >> m;

    memset(h, -1, sizeof h);
    int root;
    for (int i = 1; i <= n; i ++ )
    {
        int p;
        cin >> v[i] >> w[i] >> p;
        if (p == -1) root = i;
        else add(p, i);
    }

    dfs(root);//根节点往下走

    cout << f[root][m] << endl;

    return 0;
}
-----
#include<iostream>
#include<vector>
using namespace std;
int f[110][110];//f[x][v]表达选择以x为子树的物品,在容量不超过v时所获得的最大价值
vector<int> g[110];
int v[110],w[110];
int n,m,root;

int dfs(int x)
{
    for(int i=v[x];i<=m;i++) f[x][i]=w[x];//点x必须选,所以初始化f[x][v[x] ~ m]= w[x]
    for(int i=0;i<g[x].size();i++)
    {
        int y=g[x][i];
        dfs(y);
        for(int j=m;j>=v[x];j--)//j的范围为v[x]~m, 小于v[x]无法选择以x为子树的物品 如果小于v[x]说明连u都选不了更别说k了
        {
            for(int k=0;k<=j-v[x];k++)//分给子树son的空间不能大于j-v[x],不然都无法选根物品x
            {
                f[x][j]=max(f[x][j],f[x][j-k]+f[y][k]);//表示j-k给x的体积为在已经给够x的基础上少给了一点 j-k一定大于v[i]  (j-k)+k==j表示已经分给他的体积
                //k表示需要给到son的东西
            }
        }
    }
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int fa;
        cin>>v[i]>>w[i]>>fa;
        if(fa==-1)
            root=i;
        else
            g[fa].push_back(i);
    }
    dfs(root);
    cout<<f[root][m];
    return 0;
}



二叉苹果树https://www.acwing.com/problem/content/1076/

边作为体积 边的体积在计算的时候需要算作u的值 而不是son 的值

#include <iostream>
#include <cstring>

using namespace std;

const int N = 110, M = N << 1;

int n, m;
int h[N], e[M], w[M], ne[M], idx;
int f[N][N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u, int father)
{
    for (int i = h[u]; ~i; i = ne[i])
    {
        int ver = e[i];
        if (ver == father) continue;
        dfs(ver, 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[ver][k] + w[i]);
    }
}
int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    for (int i = 1; i < n; i ++ )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }
    dfs(1, -1);
    printf("%d\n", f[1][m]);
    return 0;
}

posted @ 2022-04-01 17:30  liang302  阅读(47)  评论(0编辑  收藏  举报