算法竞赛备赛进阶之树形DP训练

目录

1.树的最长路径

2.树的中心

3.数字转换

4.二叉苹果树

5.战略游戏

6.皇宫守卫


树形DP是一种动态规划方法,主要用于解决树形结构的问题。在树形DP中,通常会使用动态规划的思想来求解最优化问题。其核心在于通过不断地分解问题和优化子问题来解决原问题,以达到提高效率的目的。

树形DP的实现通常会使用递归或迭代的方式,其中递归的方式较为直观,而迭代的方式则可以避免递归可能导致的栈溢出问题。

在树形DP中,通常会使用状态转移方程来描述子问题与原问题之间的关系,以及如何从子问题的解推导出原问题的解。通过不断地优化子问题,最终可以求解出原问题的解。

需要注意的是,树形DP的问题通常涉及到很多细节,需要注重细节的处理,避免因为粗心大意而犯错。同时,在训练过程中,要勤于总结自己的经验和教训,不断完善自己的知识体系。

想法:

任取一个点作为起点,找到距离该点最远的一个点u。

再找到距离u最远的一点v。DFS、BFS。

1.树的最长路径

给定一棵树,树中包含n个结点(编号1~n)和n-1条无向边,每条边都有一个权值。

现在请你找出树中的一条最长路径。

换句话说,要找到两个点,使得它们的距离最远。

#include<iostream>
#include<algorithm>
#include<cstring>
​
using namespace std;
​
const int N = 10010, M = N * 2;
​
int n;
int h[N], e[M], w[M], ne[M], idx;
int ans;
​
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 father)
{
    int dis = 0;//表示当前往下走的最大长度
    int d1 = 0, d2 = 0;
    
    for(int i = h[u];i != 1; i = ne[i])
    {
        int j = e[i];
        if(j == father) continue;
        int d = dfs(j, u) + w[i];
        dist = max(dist, d);
        
        if(d >= d1)
        {
            d2 = d1;
            d1 = d;
        }
        else if(d > d2)
        {
            d2 = d;
        }
    }
    
    ans = max(ans, d1 + d2);
    
    return dist;
}
​
int main()
{
    cin >> n;
    
    memset(h, 0, 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(1, -1);
    
    cout << ans << endl;
    
    return 0;
}

2.树的中心

给定一棵树,树中包含n个结点(编号1~n)和n-1条无向边,每条边都有一个权值。

请你在树中找到一个点,使得该点到树中其它结点的最远距离最近。

输入格式

第一行包含整数n。

接下来n-1行,每行包含三个整数ai,bi,ci,表示点ai和bi之间存在一条权值为ci的边。

#include<iostream>
#include<algorithm>
#include<cstring>
​
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], p2[N], up[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;
    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];
        if(d >= d1[u])
        {
            d2[u] = d1[u];
            d1[u] = d;
            p2[u] = p1[u];
            p1[u] = j;
        }
        else if(d > d2[u])
        {
            d2[u] = d;
            p2[u] = j;
        }
    }
    
    if(d1[u] == -INF) d1[u] = d2[u] = 0;
    
    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];
        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 = INF;
    for(int i = 1;i <= n; i++)
        res = min(res, max(d1[i], up[i]));
    
    printf("%d\n", res);
    
    return 0;
}

3.数字转换

如果一个数z的约数之和y(不包括它本身)比它本身小的,那么z就可以变成y,有也可以变成x。

例如4可以变成3,1也可以变为7。

限定所有数字变换在不超过n的正整数范围内进行,求不断进行数字变化且不出现重复数字最多变换步数。

#include<iostream>
#include<algorithm>
#include<cstring>
​
using namespace std;
​
const int N = 50010;
​
int n;
int h[N], e[N], ne[N], idx;
int sum[N];
bool st[N];
int ans;
​
void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idex++;
}
​
void dfs(int u)
{
    int d1 = 0, d2 = 0;
    for(int i = h[u];i != -1; 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;
        }
    }
    
    ans = max(ans, d1 + d2);
}
​
int main()
{
    cin >> n;
    
    for(int i = 1;i <= n; i++)
        for(int j = 2;j <= m / i; j++)
            sum[i + j] += i;
    
    memset(h, -1, sizeof(h));
    for(int i = 2;i <= n; i++)
        if(i > sum[i])
        {
            add(sum[i], i);
            st[i] = true;
        }
    
    for(int i = 1;i <= n; i++)
        if(!st[i])
            dfs(i);
    
    cout << ans << endl;
    
    return 0;
}

4.二叉苹果树

有一棵二叉苹果树,如果树枝有分叉,一定是分二叉,即没有只有一个儿子在的结点

这棵树共有N个结点,编号为1至N,树根编号一定为1.

我们用一棵树枝两端连接的节点编号描述一根树枝的位置。

一棵苹果树的树枝实在是太多了,需要剪枝,但是一些树枝上长有苹果,给定需要保留的树枝数量,求最多能留下多少个苹果。

这里的保留是指最终与1号点连通。

#include<iostream>
#include<algorithm>
#include<cstring>
​
using namespace std;
​
const int N = 110, M = N * 2;
​
int n, m;
int h[a], 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 father)
{
    for(int i = h[u];i != -1; i = ne[i])
    {
        if(j == father) continue;
        dfs(e[i], u);
        for(int j = m;j >= 0; j--)
            for(int k = 0;k < j; k++)
                f[u][j] = max(f[u][j], f[u][j - k + 1] + f[e[i]][k] + w[i])
    }
}
​
int main()
{
    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] << endl;
    return 0;
}

5.战略游戏

鲍勃喜欢玩电脑游戏,特别是战略游戏,但有时他找不到解决问题的方法,这让他很伤心。

现在他有以下问题。

他必须保护一座中世纪城市,这条城市的道路构成了一棵树。

每个节点上的士兵可以观察到所有和这个点相连的边。

他必须在节点上放置最少数量的士兵,以便他们可以观察到所有的边。

你能帮助他吗?

例如,下面的树:

1463_1.jpg.gif

只需要放置 1 名士兵(在节点 1 处),就可观察到所有的边。

输入格式

输入包含多组测试数据,每组测试数据用以描述一棵树。

对于每组测试数据,第一行包含整数 N,表示树的节点数目。

接下来 N 行,每行按如下方法描述一个节点。

节点编号:(子节点数目) 子节点 子节点 …

节点编号从 0 到 N−1,每个节点的子节点数量均不超过 10,每个边在输入数据中只出现一次。

没有上司的舞会:每条边上最多选择一点,最大权值。

战略游戏:每条边上最少选择一个点。

动态规划:

  1. 状态表示f[i,j] j = 0,1

    1. 集合:所有在以i为根的子树中选,且点i的状态是j的所有选法。

    2. 属性:Min

  2. 状态计算:f[i, 0] = min(f[s1, 1] + f[s2, 1] + ....)

    f[i, 1] = min(min(f[s1, 0], f[s1, 1]) + min(f[s2, 0], f[s2, 1]) + ....)

#include<iostream>
#include<algorithm>
#include<cstring>
​
using namespace std;
​
const int N = 1510;
​
int n;
int h[N], e[N], ne[N], idx;
int f[N][2];
bool st[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] += min(f[j][0], f[j][1]);
    }
}
​
int main()
{
    while(scanf("%d", &n) == 1)
    {
        memset(h, -1, sizeof(h));
        
        idx = 0;
        
        memset(st, 0, sizeof(st));
        for(int i = 0;i < n; i++)
        {
            int id, cnt;
            scanf("%d:(%d)", &id, &cnt);
            while(cnt--)
            {
                int ver;
                scanf("%d", &ver);
                add(id, ver);
                st[ver] = true;
            }
        }
        
        int root = 0;
        while(st[root]) root++;
        
        dfs(root);
        
        printf("%d\n", min(f[root][0], f[root][1]));
    }
    
    return 0;
}

6.皇宫守卫

太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫。

皇宫以午门为起点,直到后宫嫔妃们的寝宫,呈一棵树的形状,某些宫殿间可以互相望见。

大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。

可是陆小凤手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。

帮助陆小凤布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。

输入格式

输入中数据描述一棵树,描述如下:

第一行 n,表示树中结点的数目。

第二行至第 n+1 行,每行描述每个宫殿结点信息,依次为:该宫殿结点标号 i,在该宫殿安置侍卫所需的经费 k,该结点的子结点数 m,接下来 m 个数,分别是这个结点的 m 个子结点的标号 r1,r2,…,rm。

对于一个 n 个结点的树,结点标号在 1 到 n 之间,且标号不重复。

树形DP

f[ N ] [ 3 ],表示3种状态: 0 表示父节点放了守卫, 1 表示子节点放了守卫, 2 表示自身放了守卫

则f[ u ] [ 0 ] += min(f[ j ] [ i ], f[ j ] [ 2 ]) 父节点放了守卫, 那么它的字节点可放可不放,我们取最小值

f[ u ] [ 2 ] += min(f[ j ] [ 0 ], min(f[ j ] [ 1 ], f[ j ] [ 2 ])) 自身放了守卫, 子节点可放可不放, 去3种状态的最小值

思考子节点放了守卫的情况:

枚举哪个子节点放了守卫, 即f[j ] [ 2 ], 其他子节点可放可不放, 可以先用sum求出子节点值的总和, 即f[ u ] [ 0 ],**

f[ u ] [ 0 ] - min(f[ j ] [ 1 ], - f[ j ] [ 2 ]) 表示去掉j这个子节点其他子节点的总和;

推出:f[ u ] [ 1 ] = min(f[ u ] [ 1 ], f[ j ] [ 2 ] + f[ u ] [ 0 ] - min(f[ j ] [ 1 ], f[ j ] [ 2 ]));

#include<iostream>
#include<cstring>
#include<algorithm>
​
using namespace std;
​
const int N = 1510;
​
int n;
int h[N], e[N], ne[N], idx, w[N];
int f[N][3];
bool st[N];
​
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];
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        dfs(j);
        
        f[u][0] += min(f[j][1], f[j][2]);
        f[u][2] += min(min(f[j][0], 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[i][1], f[j][2] + f[u][0] - min(f[j][1], 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;
        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][1], f[root][2]) << endl;
        
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/252441.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【理论篇】SaTokenException: 非Web上下文无法获取Request问题解决 -理论篇

在我们使用sa-token安全框架的时候&#xff0c;有时候会提示&#xff1a;SaTokenException:非Web上下文无法获取Request 错误截图&#xff1a; 在官方网站中&#xff0c;查看常见问题排查&#xff1a; 错误追踪&#xff1a; 跟着源码可以看到如下代码&#xff1a; 从源码中&a…

01 整体代码运行流程

文章目录 01 整体代码运行流程1.1 运行官方 Demo1.2 变量命名规则1.3 多线程1.4 线程锁1.5 SLAM 主类 System 01 整体代码运行流程 1.1 运行官方 Demo 以 stereo_kitti 为例&#xff0c;执行 ./stereo_kitti path_to_vocabulary path_to_settings path_to_sequence./stereo_…

大创项目推荐 深度学习 python opencv 实现人脸年龄性别识别

文章目录 0 前言1 项目课题介绍2 关键技术2.1 卷积神经网络2.2 卷积层2.3 池化层2.4 激活函数&#xff1a;2.5 全连接层 3 使用tensorflow中keras模块实现卷积神经网络4 Keras介绍4.1 Keras深度学习模型4.2 Keras中重要的预定义对象4.3 Keras的网络层构造 5 数据集处理训练5.1 …

W25Q64(模拟SPI)读写数据的简单应用

文章目录 一、W25Q64是什么&#xff1f;二、使用步骤1.硬件1.引脚说明2.硬件连接3.设备ID4.内部框架5.指令集指令集1指令集2 2.软件1.W25Q64引脚定义代码如下&#xff08;示例&#xff09;&#xff1a;2.W25Q64初始化代码如下&#xff08;示例&#xff09;&#xff1a;3.W25Q64…

在排序数组中查找元素的第一个和最后一个位置(Java详解)

一、题目描述 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。 示…

Android开发——组合函数、注解与连接Android设备

1、JetPack Compose、组合函数与注解和文本修改 1、JetPack Compose&#xff1a;Jetpack Compose 是由 Google 推出的用于构建 Android 用户界面的现代化工具包。它是一个声明式的 UI 工具包&#xff0c;用于简化 Android 应用程序的用户界面设计和开发。Jetpack Compose 采用…

02.Git常用基本操作

一、基本配置 &#xff08;1&#xff09;打开Git Bash &#xff08;2&#xff09;配置姓名和邮箱 git config --global user.name "Your Name" git config --global user.email "Your email" 因为Git是分布式版本控制工具&#xff0c;所以每个用户都需要…

02-分组查询group by和having的使用

分组查询 MySQL中默认是对整张表的数据进行操作即整张表为一组, 如果想对每一组的数据进行操作,这个时候我们需要使用分组查询 分组函数的执行顺序: 先根据where条件筛选数据,然后对查询到的数据进行分组,最后也可以采用having关键字过滤取得正确的数据 group by子句 在一条…

【STM32】STM32学习笔记-EXTI外部中断(11)

00. 目录 文章目录 00. 目录01. 中断系统02. 中断执行流程03. STM32中断04. NVIC基本结构05. NVIC优先级分组06. EXTI简介07. EXTI基本结构08. AFIO复用IO口09. EXTI框图10. 计数器模块11. 旋转编码器简介12. 附录 01. 中断系统 中断&#xff1a;在主程序运行过程中&#xff0…

easy贪吃蛇

之前承诺给出一个贪吃蛇项目。 1.EasyX库认知 有关EasyX库的相关信息&#xff0c;您可以看一下官方的文档&#xff1a;EasyX官方文档。 这里我做几点总结&#xff1a; EasyX库就和名字一样&#xff0c;可以让用户调用一些简单的函数来绘制图像和几何图形利用EasyX库可以制作…

ES6 面试题 | 15.精选 ES6 面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

知识付费平台选择指南:如何找到最适合你的学习平台?

在当今的知识付费市场中&#xff0c;用户面临的选择越来越多&#xff0c;如何从众多知识付费平台中正确选择属于自己的平台呢&#xff1f;下面&#xff0c;我们将为您介绍我有才知识付费平台相比同行的优势&#xff0c;帮助您做出明智的选择。 一、创新的技术架构&#xff0c;…

3.3【窗口】窗口的几何形状(二,窗口属性)

写在前面 应用程序使用窗口来显示内容。一些属性决定了窗口及其内容的大小和位置。其他属性决定了窗口内容的外观和解释。 了解窗口属性引用的两个坐标系非常重要。如果你知道你正在使用的坐标系,那么为你的窗口属性选择设置值会容易得多,并且会更有意义。 一,显示相关属…

【CMU 15-445】Lecture 11: Joins Algorithms 学习笔记

Joins Algorithms Nested Loop JoinNaive Nested Loop JoinBLock Nested Loop JoinIndex Nested Loop Join Sort-Merge JoinHash JoinBasic Hash JoinPartitioned Hash Join Conclusion 本节课主要介绍的是数据库系统中的一些Join算法 Nested Loop Join Naive Nested Loop Joi…

华媒舍:打造出色科普文章的10步路透社发稿手册

1.科普文章的编写是一项让科技知识更为浅显易懂重要工作。下面我们就详细介绍路透社的发稿手册&#xff0c;带来了好用的流程&#xff0c;协助作者从零开始创作出色的科普文章。 2.明确主题风格在创作科普文章以前&#xff0c;首先要明确要介绍的主题风格。选择一个有趣且适合…

verilog语法进阶,时钟原语

概述&#xff1a; 内容 1. 时钟缓冲 2. 输入时钟缓冲 3. ODDR2作为输出时钟缓冲 1. 输入时钟缓冲 BUFGP verilog c代码&#xff0c;clk作为触发器的边沿触发&#xff0c;会自动将clk综合成时钟信号。 module primitive1(input clk,input a,output reg y); always (posed…

【C++11特性篇】探究【右值引用(移动语义)】是如何大大提高效率?——对比【拷贝构造&左值引用】

前言 大家好吖&#xff0c;欢迎来到 YY 滴C11系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 目录 一.【左值&左值引用】和【右值&a…

Leetcode 删除有序数组中的重复项

给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成 示例 1&#xff1a; 输入&…

超声波清洗机哪个好?怎么样挑选清洗力比较强超声波清洗机

大部分的年轻人&#xff0c;现在因为各种原因&#xff0c;戴眼镜的人群不再是局限于学生&#xff0c;很多打工族或者是老人也戴上了眼镜&#xff0c;近视眼或者是老花眼都有。戴眼镜的人群越来越多&#xff0c;会有很多人忽视眼镜清洗这个事情&#xff0c;眼镜长时间不清洗的话…

多层记忆增强外观-运动对齐框架用于视频异常检测 论文阅读

MULTI-LEVEL MEMORY-AUGMENTED APPEARANCE-MOTION CORRESPONDENCE FRAMEWORK FOR VIDEO ANOMALY DETECTION 论文阅读 摘要1.介绍2.方法2.1外观和运动对其建模2.2.记忆引导抑制模块2.3. Training Loss2.4. Anomaly Detection 3.实验与结果4.结论 论文标题&#xff1a;MULTI-LEVE…