acwing算法基础之动态规划--数位统计DP、状态压缩DP、树形DP和记忆化搜索

目录

  • 1 基础知识
  • 2 模板
  • 3 工程化

1 基础知识

暂无。。。

2 模板

暂无。。。

3 工程化

题目1:求a~b中数字0、数字1、…、数字9出现的次数。

思路:先计算1~a中每位数字出现的次数,然后计算1~b-1中每位数字出现的次数,两个相减即是最终答案。

那么,如何计算1~a中每位数字出现的次数呢?

首先,将a的每一位存入向量num中,例如a=1234567,那么num为,
在这里插入图片描述
考虑如下两个子问题,

  1. 1~a中数字0出现的次数。
  2. 1~a中数字5出现的次数。为啥选择数字5呢?因为1到9中的任意一个数都和5等价。

对于问题1:1~x中数字0出现的次数。

记num中有n位,从第0位不考虑,因为第0位不可能取到0,即数字首位不能为0,例如0123,这样的数字是不合法的,应该表示成123。所以i从1到n-1,即for (int i = 1; i < n; ++i)。考虑如下情况,

  1. 当num的第i位取0且其左侧部分不取L时,有多少个数?
    在这里插入图片描述
    左边部分的方案数为:1~L-1,共L-1个数。
    右边部分的方案数为:0~ 1 0 n − 1 − i − 1 10^{n-1-i}-1 10n1i1,共 1 0 n − 1 − i 10^{n-1-i} 10n1i个数。
    故总方案数为: ( L − 1 ) ⋅ 1 0 n − 1 − i (L-1)\cdot 10^{n-1-i} (L1)10n1i

  2. 当num的第i位取0且其左侧部分取L时,
    2.1 如果第i位原先数字等于0,那么左边方案数=1,右边方案数=0~R,共R+1个,总方案数=1 * (R+1) = R+1。
    2.2 如果第i位原先数字大于0,那么左边方案数=1,右边方案数=0~ 1 0 n − 1 − i − 1 10^{n-1-i}-1 10n1i1,共 1 0 n − 1 − i 10^{n-1-i} 10n1i个数。

对于问题2:1~x中数字5出现的次数。

如果num的第0位等于5,有R+1个方案数。如果num的第0位大于5,有 1 0 n − 1 10^{n-1} 10n1个数。

i从1到n-1,即for (int i = 1; i < n; ++i)。考虑如下情况,

  1. 当num的第i位取5且其左侧部分不取L时,有多少个数?
    在这里插入图片描述
    左边部分的方案数为:0~L-1,共L个数。
    右边部分的方案数为:0~ 1 0 n − 1 − i − 1 10^{n-1-i}-1 10n1i1,共 1 0 n − 1 − i 10^{n-1-i} 10n1i个数。
    故总方案数为: L ⋅ 1 0 n − 1 − i L\cdot 10^{n-1-i} L10n1i

  2. 当num的第i位取5且其左侧部分取L时,
    2.1 如果第i位原先数字等于5,那么左边方案数=1,右边方案数=0~R,共R+1个,总方案数=1 * (R+1) = R+1。
    2.2 如果第i位原先数字大于5,那么左边方案数=1,右边方案数=0~ 1 0 n − 1 − i − 1 10^{n-1-i}-1 10n1i1,共 1 0 n − 1 − i 10^{n-1-i} 10n1i个数。

综合上述,C++代码如下,

#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>using namespace std;
int a, b;int compute_R(const vector<int>& num, int i) {//计算第i位右侧数字大小int R = 0;for (int k = i + 1; k < num.size(); ++k) {R = R * 10 + num[k];}return R;
}int compute_L(const vector<int>& num, int i) {//计算第i位左侧数字大小int L = 0;for (int k = 0; k < i; ++k) {L = L * 10 + num[k];}return L;
}int compute_cnt(int a, int x) {//计算1~a中x出现的次数if (a <= 0) return 0;vector<int> num; //把a转化为num,高位在前int t = a;while (t) {num.emplace_back(t % 10);t /= 10;}reverse(num.begin(), num.end()); //保证高位在前int n = num.size(); //a总共有n位int res = 0; //存储1~a中x出现的次数//考虑第0位取x的情况if (x != 0) {if (num[0] == x) {//有R+1个方案int R = compute_R(num, 0);res += R + 1;} else if (num[0] > x) {//有10^(n-1)个方案数res += pow(10, n - 1);}}//考虑第i位取x的情况for (int i = 1; i < n; ++i) {//计算情况1中x出现的次数if (x == 0) {int L = compute_L(num, i);res += (L - 1) * pow(10, n - 1 - i);} else {int L = compute_L(num, i);res += L * pow(10, n - 1 - i);}//计算情况2中x出现的次数if (num[i] == x) {int R = compute_R(num, i);res += R + 1;} else if (num[i] > x) {res += pow(10, n - 1 - i);}}return res;//返回1~a中x出现的次数
}int main() {while (cin >> a >> b, a || b) {if (a > b) swap(a, b); //保证b是大数for (int x = 0; x < 10; ++x) {int cnt1 = compute_cnt(b, x); //计算1~b中x出现的次数int cnt2 = compute_cnt(a - 1, x); //计算1~a中x出现的次数cout << cnt1 - cnt2 << " ";}cout << endl;}return 0;
}

题目2:在N * M的棋盘下,摆放1*2的矩形块,可以竖着摆,也可以横着摆,要求摆满棋盘,求有多少种摆放方案。

解题思路:状态压缩类DP。

规定横着摆放的1*2的矩形块,第一个小方格为起点方格,第二个小方格为终点方格,如下图所示,
在这里插入图片描述
f[i][j]状态定义:前i-1列已经摆放完毕,第i列中终点方格的状态为j,的方案数。

其中终点方格的状态j是指,从第0行到第n-1行,有终点网格的记作1,没有终点网格的记作0。比如下图中第1列的终点方格的状态是 ( 1001 ) 2 (1001)_2 (1001)2,即十进制中的9。

在这里插入图片描述
f[i][j]的状态转移:f[i-1][k],即前i-2列已经摆放完毕,第i-1列中终点网格的状态为k。k需要满足两个条件:

  1. 第i-1列中的终点网格和起点网格不能重合,其中终点网格的状态为k,起点网格的状态为j,也即k & j == 0
  2. 连续的空网格的数目不能是奇数,否则竖着摆放不下1*2的矩形块。即数k | j二进制表示中连续0的数目不能是奇数。

故,综合上述,状态转移方程为,
f [ i ] [ j ] = ∑ k f [ i − 1 ] [ k ] f[i][j] = \sum_kf[i-1][k] f[i][j]=kf[i1][k]

初始化f[0][0] = 1

最终答案f[M][0],也即第0列、第1列、…、第M-1列均摆放完毕,第M列中终点格子的状态为0的方案数。

C++代码如下,

#include <iostream>
#include <vector>
#include <cstring>using namespace std;const int N = 12, M = 1 << N;
bool st[M];
vector<vector<int>> last(M);
long long f[N][M];
int n, m;int main() {while (cin >> n >> m, n || m) {//n*m的棋盘//步骤1:把状态0~1<<n-1中满足连续0个数为偶数的状态标识出来memset(st, 0, sizeof st); //多组测试数据,所以要初始化for (int i = 0; i < 1 << n; ++i) {//数i的二进制表示中,连续0的个数是不是都是偶数bool flag = true;int cnt = 0; //连续0的个数for (int j = 0; j < n; ++j) {if (i >> j & 1) {//第j位为1,前面的连续0之和为cntif (cnt & 1) {//如果cnt是奇数flag = false;break;}cnt = 0;} else {cnt += 1;}}if (cnt & 1) flag = false;//判断最后一个连续0的数目st[i] = flag; //true表示状态i中连续0的个数都是偶数。//cout << "i = " << i << ", st[i] = " << st[i] << endl;}//步骤2:f[i][j]和f[i-1][k],存储j满足要求的kfor (int j = 0; j < 1 << n; ++j) {last[j].clear(); //多组测试数据,所以要清空for (int k = 0; k < 1 << n; ++k) {if ((k & j) == 0 && st[k | j]) {last[j].emplace_back(k);}}}//步骤3:正式dpmemset(f, 0, sizeof f);f[0][0] = 1;for (int i = 1; i <= m; ++i) {for (int j = 0; j < 1 << n; ++j) {for (auto k : last[j]) {f[i][j] += f[i-1][k];}}}cout << f[m][0] << endl;}return 0;
}

题目3:给定n*n的矩阵,表示n个结点两两之间的距离,求从0号结点出发到达第n-1号结点,经过每一个结点一次的路径的最小距离。

解题思路:状态压缩DP。

状态定义,f[i][j],即:所有从第0号结点走到第j号结点,且走过的结点是i的路径,的最小值。

其中i的二进制表示中第0位为1表示,经过第0号结点,否则不经过第0号结点。
i的二进制表示中第1位为1表示,经过第1号结点,否则不经过第1号结点。
i的二进制表示中第2位为1表示,经过第2号结点,否则不经过第2号结点。
……
i的二进制表示中第n-1位为1表示,经过第n-1号结点,否则不经过第n-1号结点。

注意,只有满足(i & 1) == 1 && (i & (1 << j)) == (1 << j)时,状态f[i][j]才合法。

状态转移,考虑最后一步咋走的,即f[last][k] + d[k][j],其中i包含结点k时才合法,即(i & (1 << k)) == (1 << k),且last = i - (1 << j)

故,综合上述,状态转移方程为,
f [ i ] [ j ] = m i n k { f [ l a s t ] [ k ] + d [ k ] [ j ] } f[i][j] = \underset {k}{min} \{ f[last][k] + d[k][j] \} f[i][j]=kmin{f[last][k]+d[k][j]}
l a s t = i − ( 1 < < k ) last = i - (1 << k) last=i(1<<k)

初始化,f[1][0] = 0,其余初始化为正无穷大。

最终答案,f[(1 << n) - 1][n-1]

C++代码如下,

#include <iostream>
#include <cstring>using namespace std;const int N = 21, M = 1 << N;
int f[M][N];
int n;
int d[N][N];int main() {cin >> n;for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {cin >> d[i][j];}}memset(f, 0x3f, sizeof f);f[1][0] = 0;for (int i = 0; i < (1 << n); ++i) {for (int j = 0; j < n; ++j) {//从0出发到达j,经过的结点是iif ((i & 1) == 1 && (i & (1 << j)) == (1 << j)) {//f[i][j]是合法的for (int k = 0; k < n; ++k) {if ((i & (1 << k)) == (1 << k)) {//i中有kint last = i - (1 << j);f[i][j] = min(f[i][j], f[last][k] + d[k][j]);}}}}}cout << f[(1 << n) - 1][n-1] << endl;return 0;
}

题目4:没有上司的舞会。直接上司和员工不能同时选择,求最大值。

解题思路:树形DP,从根结点递归处理。

状态定义,有,

  1. f[i][0],所有以i为根的子树中选择,但不选择结点i的方案数中的最大值。
  2. f[i][1],所有以i为根的子树中选择,且选择结点i的方案数中的最大值。

状态转移,

f [ i ] [ 0 ] = ∑ j m a x { f [ j ] [ 0 ] , f [ j ] [ 1 ] } f[i][0] = \sum_j max\{ f[j][0], f[j][1] \} f[i][0]=jmax{f[j][0],f[j][1]}
其中结点j为结点i的子结点。
f [ i ] [ 1 ] = w [ i ] + ∑ j f [ j ] [ 0 ] f[i][1] = w[i] + \sum_j f[j][0] f[i][1]=w[i]+jf[j][0]
其中结点j为结点i的子结点。

初始化,二维数组初始化为0。

记根结点为start,那么最终答案res = max(f[start][0], f[start][1])

C++代码为,

#include <iostream>
#include <cstring>
#include <vector>using namespace std;const int N = 6010;int n, start;
bool has_father[N];
int happy[N];
int f[N][2];
vector<vector<int>> sons(N);void dfs(int i) {f[i][1] = happy[i];for (auto j : sons[i]) {dfs(j);f[i][1] += f[j][0];f[i][0] += max(f[j][0], f[j][1]);}return;
}int main() {cin >> n;for (int i = 1; i <= n; ++i) cin >> happy[i];for (int i = 1; i < n; ++i) {int a, b;cin >> a >> b;has_father[a] = true;sons[b].emplace_back(a);}//找到根结点start = -1;for (int i = 1; i <= n; ++i) {if (has_father[i] == false) {start = i;break;}}dfs(start);cout << max(f[start][0], f[start][1]) << endl;return 0;
}

题目5:滑雪。n*m的网格,可以往上下左右方向尝试,只能从高处滑到低处。求路径最长值。

思路:记忆化搜索,状态先全部初始化为-1,然后递归每一个位置,如果f[i][j] != -1,则说明它被计算过了,直接返回。

状态表示,f[i][j]:从(i,j)开始滑的最长路径值。

状态转移,有:

  1. 如果能往上滑,即(i-1,j)在网格内且h[i][j] > h[i-1][j],有f[i-1][j] + 1
  2. 如果能往下滑,即(i+1,j)在网格内且h[i][j] > h[i+1][j],有f[i+1][j] + 1
  3. 如果能往左滑,即(i-1,j)在网格内且h[i][j] > h[i-1][j],有f[i-1][j] + 1
  4. 如果能往右滑,即(i+1,j)在网格内且h[i][j] > h[i+1][j],有f[i+1][j] + 1

初始化,所有状态均设置为-1。

答案,即所有状态的最大值。

C++代码如下,

#include <iostream>
#include <cstring>using namespace std;const int N = 310;
int n, m;
int f[N][N], h[N][N];int dirs[4][2] = {{1,0}, {-1,0}, {0,1}, {0,-1}};int dfs(int i, int j) {if (f[i][j] != -1) return f[i][j];f[i][j] = 1;for (int k = 0; k < 4; ++k) {int x = i + dirs[k][0], y = j + dirs[k][1];if (x >= 1 && x <= n && y >= 1 && y <= m && h[i][j] > h[x][y]) {f[i][j] = max(f[i][j], dfs(x, y) + 1);}}return f[i][j];
}int main() {cin >> n >> m;for (int i = 1; i <= n; ++i) {for (int j = 1; j <= m; ++j) {cin >> h[i][j];}}memset(f, -1, sizeof f);int res = 0;for (int i = 1; i <= n; ++i) {for (int j = 1; j <= m; ++j) {res = max(res, dfs(i, j));}}cout << res << endl;return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++基础 -29- 友元类

友元类格式 friend class person2;类的友元类访问类的全部成员 #include "iostream"using namespace std;class person1 { public:int a;protected:int b;private:int c;friend class person2; };class person2 { public:void test(){person1 a;a.a 100;a.b 200…

校园局域网规划与设计(cisco仿真模拟)

摘 要 随着网络技术的发展&#xff0c;校园网的建设已经进入到一个蓬勃发展的阶段。校园网的建成和使用&#xff0c;对于提高教学和科研的质量、改善教学和科研条件、加快学校的信息化进程&#xff0c;开展多媒体教学与研究以及使教学多出人才、科研多出成果有着十分重要而深远…

05-建造者模式-C语言实现

UML类图&#xff1a; 代码实现&#xff1a; #include <stdio.h> #include <stdlib.h>// 产品类 typedef struct {char* part1;char* part2;char* part3; } Product;// 抽象建造者类 typedef struct {void (*buildPart1)(void*, const char*);void (*buildPart2)(v…

leetCode 494.递增子序列 + 回溯算法 + 图解 + 笔记

给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素&#xff0c;如出现两个整数相等&#xff0c;也可以视作递增序列的一种特殊情况。 示例 1&#…

Python项目管理利器poetry我愿称之为神!

MongoDB是一种流行的NoSQL数据库&#xff0c;它以灵活的文档结构存储数据。MongoDB 提供了可用于 32 位和 64 位系统的预编译二进制包&#xff0c;你可以从MongoDB官网下载安装&#xff0c;MongoDB 预编译二进制包下载地址&#xff1a; https://www.mongodb.com/try/download/…

【Linux】信号概念和信号的产生

文章目录 一、什么是信号&#xff1f;1.signal系统调用2.从硬件解析键盘数据如何输入给内核3.同步和异步 二、信号的产生1.键盘组合键2. kill命令3.系统调用接口3.1kill3.2 raise3.3abort 4.异常5.软件条件 重谈core dump标志位 一、什么是信号&#xff1f; 以日常为例&#x…

Elasticsearch:么是向量嵌入?

向量嵌入定义 向量嵌入 (vector embeddings) 是一种将单词、句子和其他数据转换为捕获其含义和关系的数字的方法。 它们将不同的数据类型表示为多维空间中的点&#xff0c;其中相似的数据点更紧密地聚集在一起。 这些数字表示可以帮助机器更有效地理解和处理这些数据。 单词和…

VS安装QT VS Tools编译无法通过

场景&#xff1a; 项目拷贝到虚拟机内部后&#xff0c;配置好相关环境后无法编译&#xff0c;安装QT VS Tools后依旧无法编译&#xff0c;查找资料网上说的是QT工具版本不一致导致的&#xff0c;但反复试了几个版本后依旧无法编译通过。错误信息如下&#xff1a; C:\Users\Ad…

OpenTelemetry系列 - 第1篇 相关概念

目录 一、背景二、概念2.1 Traces & Span2.2 Metrics2.3 Logs2.4 Baggage2.5 OTel2.6 OTLP2.7 Resources2.8 Instrumentation Scope2.9 Sampling 三、核心组件 一、背景 OpenTelemetry是一个可观察性框架和工具包&#xff0c;旨在创建和管理遥测数据&#xff0c;如跟踪、指…

Monocle 3 | 太牛了!单细胞必学R包!~(五)(差异分析之聚类比较与模块鉴定)

1写在前面 准备出去玩耍了&#xff0c;今天就不废话了&#xff0c;直接上主题吧。&#x1f973; monocle3做差异分析也是牛的一米&#xff01;~&#x1f33e; 2用到的包 rm(list ls())library(tidyverse)library(monocle3) 3示例数据 我们还是载入之前用过的一个数据集吧。&am…

HarmonyOs 4 (三) ArkTS语言

目录 一 认识ArkTs语言1.1 ArkTs1.2 基本结构 二 基本语法2.1 声明式UI2.1.1 创建组件2.1.1.1 无参数2.1.1.2 有参数2.1.1.3 组件样式2.1.1.4 组件方法2.1.1.5 组件嵌套 2.1.2 自定义组件2.1.2.1 基本结构2.1.2.2 成员函数/变量2.1.2.3 自定义组件的参数规定2.1.2.4 Build函数2…

高效转码工具Compressor for Mac,让视频处理更轻松

在现如今的数字时代&#xff0c;视频内容已经成为人们生活中不可或缺的一部分。无论是在社交媒体上分享生活点滴&#xff0c;还是在工作中制作专业的营销视频&#xff0c;我们都希望能够以高质量、高效率地处理和传输视频文件。而Compressor for Mac作为一款强大的视频转码工具…

vivado实现分析与收敛技巧6-策略建议

典型时序收敛策略需运行大量实现策略并选取其中最佳的策略以供在实验室内应用。 ML 策略同样可选 &#xff0c; 且只需您运行3 项策略即可达成类似的 QoR 收益。这些策略使用机器学习来检验布线后设计的各项功能特性 &#xff0c; 以便预测相同设计上不同策略的性能。在 repo…

unity3d c#代码变更文本颜色,可选多参数,委托invoke延迟调用函数

[SerializeField] private Text warning; Color color ;warningOpen("注册成功", closeTime: 1.5f);warningOpen("登录成功", "green", 1.5f);public void warningOpen( string warn, string tmp"red", float closeTime5f ){warnin…

常用装备生产ERP有哪几种?有哪些作用

装备生产业务涉及原材料采购、车间排产、班组生产评估、派工单、接单报价、委外发料、库存盘点、设备台账、图纸设计等诸多环节&#xff0c;而各环节数据的共享问题普遍存在于装备生产企业内部&#xff0c;同时也直接影响企业的生产效率和整体效益等。 企业外部环境的变化和行…

探索意义的深度:自然语言处理中的语义相似性

一、说明 语义相似度&#xff0c;反应出计算机对相同内容&#xff0c;不同表达的识别能力。因而识别范围至少是个句子&#xff0c;最大范围就是文章&#xff0c;其研究方法有所区别。本文将按照目前高手的研究成绩&#xff0c;作为谈资介绍给诸位。 二、语义相似度简介 自然语言…

特种电源模块怎么测试?用电源模块测试系统测试需要哪些流程?

什么是特种电源? 特种电源即特殊种类的电源&#xff0c;是能够为各种特殊场合或应用提供稳定、可靠电力的电源设备。特种电源的特殊性主要体现在输出电压特别高&#xff0c;输出电流特别大&#xff0c;对稳定度、动态响应及纹波要求特别高等。 根据应用场景和功能&#xff0c;…

什么是Anaconda

Anaconda的安装也很方便。打开这个网站Anaconda下载&#xff0c;然后安装即可。 Anaconda可以帮助我们解决团队之间合作的包依赖管理问题。在没有使用Anaconda之前&#xff0c;如果你的Python程序想让你的同事运行&#xff0c;那么你的同事可能会遇到很多包依赖问题&#xff0…

景联文科技数据标注平台助力AI数据实现价值最大化

随着人工智能技术不断进步&#xff0c;应用领域不断拓宽&#xff0c;对于高质量、大规模标注数据的需求也在不断增加。 数据标注是人工智能行业的基石。机器学习需要运用海量的有效数据来做支撑&#xff0c;而这些数据就需要我们的标注员对其进行分析和处理&#xff0c;想要得到…

系列十七、理解SpringBoot中的starter 自定义一个starter

一、概述 作为后端Java程序员&#xff0c;基本上公司的日常开发都是基于SpringBoot进行的&#xff0c;我们使用SpringBoot也是沉醉于它的各种各样的starter带给我们的便利&#xff0c;这些starter为我们带来了众多的自动化配置&#xff0c;通过这些自动化配置&#xff0c;我们可…