算法提高-动态规划-树形DP

AcWing 1072. 树的最长路径

#include <iostream>
#include <cstring>

const int N = 1e4 + 10, M = N << 1;//初始不确定树的拓扑结构,因此要建立双向边
int h[N], w[M], e[M], ne[M], idx;
int f[N];//表示以节点i为端点 到i节点的 最长路径
int ans =  -1;
int n;


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

int dfs(int u, int fa)
{
    
    
    int d1 = 0, d2 = 0;
    for (int i = h[u]; ~i; i = ne[i])
    {
    
    
        int j = e[i];
        if (j == fa) continue;
        int d = dfs(j, u) + w[i];
        if (d >= d1) d2 = d1, d1 = d;
        else if (d > d2) d2 = d;
    }
    ans = std::max(ans, d1 + d2);
    return d1;
}

void solve()
{
    
    
    std::cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 0; i < n - 1; ++ i) //n - 1条边不是n条。。
    {
    
    
        int a, b, c;
        std::cin >> a >> b >> c;
        add(a,b, c), add(b, a, c);
    }
    dfs(1, -1);//我们可以任意选取一个点作为根节点,这样整棵树的拓扑结构被唯一确定下来了
    std::cout << ans;
    return ;
}
int main()
{
    
    
    solve();
    return 0;
}

AcWing 1073. 树的中心【父子节点相互传更新信息】

#include <iostream>
#include <cstring>

const int N = 1e4 + 10, M = N << 1, INF = 0x3f3f3f3f;
int d1[N], d2[N], up[N];
int p1[N];//记录最长路是从哪个子节点转移过来的
int h[N], e[M], ne[M], w[M], idx;
bool is_leaf[N];
int n;

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


int dfs_down(int u, int fa)
{
    
    
    d1[u] = d2[u] = -INF;
    for (int i = h[u]; ~i; i = ne[i])
    {
    
    
        int j = e[i];
        if (j == fa) continue;
        int d = dfs_down(j, u) + w[i];
        if (d >= d1[u])
        {
    
    
            d2[u] = d1[u], d1[u] = d, p1[u] = j;
        }
        else if (d > d2[u]) d2[u] = d;//不需要开一个p2[u]记录次短路径是从哪个点转移过来的,如果最长路径不行(经过j),那么次长路径肯定不经过j
                                                                                    //如果最长路径行(不经过j),那么也不用选择次长路径了
    }
    
    if (d1[u] == -INF) 
    {
    
    
        d1[u] = d2[u] = 0;
        is_leaf[u] = true;
    }
    
    return d1[u];
}

void dfs_up(int u, int fa)
{
    
    
    for (int i = h[u]; ~i; i = ne[i])
    {
    
    
        int j = e[i];
        if (j == fa) continue;
        if (p1[u] == j) up[j] = std::max(up[u], d2[u]) + w[i];//父节点更新子节点
        else up[j] = std::max(up[u], d1[u]) + w[i];
        dfs_up(j, u);
    }
}

void solve()
{
    
    
    memset(h, -1, sizeof h);
    std::cin >> n;
    for (int i = 0; i < n - 1; ++ i)
    {
    
    
        int a, b, c;
        std::cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }
    
    dfs_down(1, -1);//在n个点n-1条无向边的图中,单拎起任何一个点都可以是一颗树的根节点
    dfs_up(1, -1);
    
    int res = INF;
    for (int i = 1; i <= n; ++ i)//遍历所有节点,
    {
    
    
        if (is_leaf[i]) res = std::min(res, up[i]);
        else res = std::min(res, std::max(d1[i], up[i]));//不是叶子节点才能又向上又向下
    }
    std::cout << res ;
    return ;
}

int main()
{
    
    
    solve();
    return 0;
}

AcWing 1075. 数字转换

#include <iostream>
#include <cstring>
using namespace std;
const int N = 5 * 1e4 + 10, INF = 0x3f3f3f3f;

int sum[N];
bool st[N];
int h[N], e[N], ne[N], idx;
int n;
int ans;

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

int dfs(int u) {
    
    
    int d1 = 0, d2 = 0;//不能定义成-INF
    for (int i = h[u]; ~i; i = ne[i]) {
    
    
        int j = e[i];
        int d = dfs(j) + 1;
        if (d >= d1) d2 = d1, d1 = d;
        else if (d >= d2) d2 = d;
    }
//  if (d1 = -INF) d1 = d2 = 0;
    ans = max(ans, d1 + d2);
    return d1;
}


int main()
{
    
    
    std::cin >> n;
    memset(h, -1, sizeof h);
    //增加代码的扩展性(可以适应更大的数据范围)降低复杂度
    for (int i = 1; i <= n; ++ i)//我们这里逆向思维,求i可以组成多少个数的约数,而不是试除法求约数(即求数i有哪些约数)
        for (int j = 2; j <= n / i; ++ j)//因为题目说了约数和不包含自身,所以枚举的时候不能从1开始枚举
        sum[i * j] += i;
    
    for (int i = 2; i <= n; ++ i)
    {
    
    
        if (sum[i] < i) 
        {
    
    
            add(sum[i], i);
            st[i] = true;//我们转移的时候从小到大转移,将大的变成小的子节点,这样才能找到最长的
        }
    }
    

    for (int i = 1; i <= n; ++ i)//有很多棵树,每棵树都有多种根节点,因此干脆把每个点都便利一遍
    {
    
    
        if (!st[i]) dfs(i);
    }
    
    std::cout << ans;
    return 0;
}

AcWing 1074. 二叉苹果树

在这里插入图片描述

依赖背包问题,选择子节点必须选择它的父节点

#include <iostream>
#include <cstring>
const int N = 1e2 + 10, M = N << 1;//这里我不知道为什么要设计成无向图
int n, m;
int h[N], e[M], ne[M], w[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 fa)
{
    
    
    for (int i = h[u]; ~i; i = ne[i])
    {
    
    
        int ver = e[i];
        //无向图,子节点会遍历到父节点
        if (ver == fa) continue;
        dfs(ver, u);//给当前的子节点ver赋值
        for (int j = m; j >= 0; -- j)
            for (int k = 0; k <= j - 1; ++ k)//给父节点到当前的节点留一条边
                {
    
    
                    f[u][j] = std::max(f[u][j], f[u][j - k - 1] + f[ver][k] + w[i]);
                }
    }
}


int main()
{
    
    
    std::cin >> n >> m;
    memset(h, -1, sizeof h);
    for (int i = 0; i < n - 1; ++ i)//题目说了n-1行
    {
    
    
        int a, b, c;
        std::cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);//不建两条边会wa,不知道为啥非要变成无向图
    }
    
    dfs(1, -1);
    std::cout << f[1][m];
    return 0;
}

AcWing 323. 战略游戏【状态机模型+树形dp】

这题的input很恶心
在这里插入图片描述

#include <iostream>
#include <cstring>

const int N = 1500 + 10;
int h[N], e[N], ne[N], idx;
int f[N][2];//以i为根的子树,在i节点放或不放哨兵所最少需要的哨兵数量
bool not_root[N];
int n;

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

void dfs(int u)
{
    
    
    f[u][0] = 0, f[u][1] = 1;
    for (int i = h[u]; ~i; i = ne[i])
    {
    
    
        int j = e[i];
        dfs(j);
        f[u][0] += f[j][1];
        f[u][1] += std::min(f[j][1], f[j][0]);
    }
}
int main()
{
    
    
    //这题的输入输出好恶心
    while (~(scanf("%d", &n)))
    {
    
    
        //多组数据需要初始化
        // memset(f, 0, sizeof f);dfs的时候会初始化的
        memset(not_root, false, sizeof not_root);
        memset(h, -1, sizeof h), idx = 0;
        int a, b, size;
        for (int i = 0; i < n; ++ i)
        {
    
    
            scanf("%d:(%d)", &a, &size);
            while (size -- )
            {
    
    
                scanf("%d", &b);
                add(a, b);
                not_root[b] = true;
            }
        }
        
        
        int root = 0;
        while (not_root[root]) root ++ ;
        dfs(root);
        std::cout << std::min(f[root][0], f[root][1]) << std::endl;
    }
    return 0;
}

AcWing 1077. 皇宫看守【状态机+树形dp】

战略游戏是每个边都要有人看守,
本题是每个点都要有人看守
在这里插入图片描述



/*
以下注释为早期笔记,希望对你有所帮助

状态机 + 树形Dp问题
状态表示:
    f(i, 0):第i号结点被他的父结点安排的守卫看住的方案数
    f(i, 1):第i号结点被他的子结点安排的守卫看住的方案数
    f(i, 2):第i号结点自己安排守卫看住的方案数
状态计算:(j是i的子结点)
    f(i, 0) = sum{min(f(j,1), f(j,2))}
        i是被他父结点看住的,那他的子结点要么自己看自己,要么被自己的子结点看住
    f(i, 1) = min{w(k) + f(k, 2) - sum{min(f(j,1), f(j,2))}}
        i如果是被子结点看住的,那么就要枚举他是被哪个子结点看住的所有方案,对所有方案求最小值
        这里的sum不包括j==k的情况,因此需要手动额外减去
    f(i, 2) = sum{min(f(j,0), f(j,1), f(j,2))} + w(u)
        i是被自己看住的,那他的子结点可以被父结点看住,可以自己看自己,也可以被自己的子结点看住
*/



#include <iostream>
#include <cstring>

const int N = 1500 + 10;
int f[N][3];//以i为根节点,如果是012的情况下,最少的经费是多少
bool not_root[N];
int h[N], w[N], e[N], ne[N], idx;

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

void dfs(int u)
{
    
    
    int sum = 0;
    f[u][0] = 0, f[u][1] = 1e9, f[u][2] = w[u];//f[u][1]是子节点看守的,因此我们要找一个最小值
    for (int i = h[u]; ~i; i = ne[i])
    {
    
    
        int j = e[i];
        dfs(j);
        f[u][0] += std::min(f[j][1], f[j][2]);
        f[u][2] += std::min(std::min(f[j][0], f[j][1]), f[j][2]);//自己看自己的话,子节点什么状态都无所谓
        sum += std::min(f[j][1], f[j][2]);//没有父节点的看照下,子节点最少需要多少费用
    }
    for (int i = h[u]; ~i; i = ne[i])
    {
    
    
        int j = e[i];
        f[u][1] = std::min(f[u][1], sum - std::min(f[j][1], f[j][2]) + f[j][2]);//要从子树中选一个花费最小的,看看是选那个子节点看照自己
    }
}

int main()
{
    
    
    int n;
    std::cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 0; i < n; ++ i)
    {
    
    
        int id, cost, cnt;
        std::cin >> id >> cost >> cnt;
        w[id] = cost;
        for (int j = 0; j < cnt; ++j)
        {
    
    
            int ver;
            std::cin >> ver;
            add(id, ver);
            not_root[ver] = true;
        }
    }
    
    //本题节点的下标从1开始,因此root初始值为1
    int root = 1;
    while (not_root[root]) root ++;
    dfs(root);
    std::cout << std::min(f[root][1], f[root][2]);//root没有父节点
    return 0;
}

猜你喜欢

转载自blog.csdn.net/chirou_/article/details/131845780
今日推荐