前言

如果你对这篇文章可感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。

本文是「动态规划」系列文章的第二篇,主要聚焦于常见线性 DP 模型的讲解。

线性 DP 是所有 DP 模型中最为常见与基础的模型,也是面试中最频繁考察的动态规划模型,因此我们将其放在本系列的第二篇进行介绍。

本文一共分成四个部分,具体内容框架如下所示:

动态规划解题思路回顾

在正式开始线性 DP 的介绍前,我们需要回忆一下「动态规划(一)」中的主要内容,即动态规划的解题思路。

动态规划主要分为两个核心部分,一是确定「DP 状态」,二是确定「DP 转移方程」。

「DP 状态」的确定有两大原则,一是「最优子结构」,二是「无后效性」,简要概括就是将原问题划分为多个子问题,且「大规模子问题最优值」仅与「小规模子问题最优值」有关,与「小规模子问题最优值」是如何得到的无关。

此处的「大规模」与「小规模」,就是「DP 问题」的关键所在,也是 DP 问题分类的重要标准。

确定完「DP 状态」后,只需要分类讨论、细心枚举各种情况,即可得到「DP 转移方程」。

大家在做题时,需要仔细体会每道题的「DP 状态」与「DP 转移方程」,认真考虑这两部分是通过怎样的思考得出的,才能不断加深对「动态规划」问题的理解。

线性 DP 概述

线性划分 DP 规模的动态规划算法被统称为线性 DP。在线性 DP 中,DP 状态从「小规模」转移到「大规模」的同时, DP 状态沿着各个维度线性增长。

线性 DP 的常见分类如下,其中「最长上升子序列 LIS」、「最长公共子序列 LCS」、「数字三角形」为基础线性 DP 模型,将于本文下一部分介绍,而「背包问题」由于种类繁多,将放到后续系列文章中讲解。

基础模型

熟练掌握「动态规划」问题的基础模型对于后续的习题练习非常重要,因此对于下述的三个模型,大家需要仔细把握其思想,尤其是「DP 状态」的设立思想。

300. 最长上升子序列(LIS)

题目描述

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

模型讲解

求一个无序数组的最长上升子序列,如果是第一次见到这样的问题,那肯定没有什么思路,这个时候我们可以减小长度,从小规模的问题着手思考。

如果长度为 1,答案等于多少?

很明显,答案也等于 1。那如果长度为 2 呢?

那我们需要考虑第二个数是否比第一个数大,如果比第一个数大,则答案为 2,否则为 1。那如果长度为 3 呢?

那我们需要枚举第三个数是否比第二个数或第一个数大,如果比它大,则可以直接从第二个数或第一个数的答案直接转移而来。因此我们可以如下制定「DP 状态」,f[i]f[i]f[i] 表示仅考虑前 iii 个数,以第 iii 个数为结尾的最长上升子序列的最大长度。

由此我们可以推出如下转移方程:
f[i]=max(1,f[j]+1),a[j]<a[i],j<if[i]=max(1, f[j]+1),a[j]<a[i],j<i f[i]=max(1,f[j]+1),a[j]<a[i],j<i

该模型「DP 状态」的关键在于固定了最后一个数字,而这样做的原因在于对于一个最长上升子序列,我们只需要关注它最后一个数字,对于其前面的数字我们并不关心。

该模型的时间复杂度为 O(n2)O(n^2)O(n2),其中 nnn 为数组长度。另外,该模型还可以用二分优化到 O(nlog(n))O(nlog(n))O(nlog(n)),大家感兴趣的话可以自行了解。

C++ 代码实现

class Solution {public:int lengthOfLIS(vector<int>& nums) {int sz = nums.size(), ans = 0;vector<int> f(sz, 0);for(int i = 0; i < sz; i++) {int tmp = 1;for(int j = i-1; j >= 0; j--) {if(nums[i] > nums[j])tmp = max(tmp, f[j]+1);}  f[i] = tmp;ans = max(ans, tmp);}return ans;}
};

1143. 最长公共子序列(LCS)

题目描述

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的「子序列」是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

示例 1

输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace",它的长度为 3。

示例 2

输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc",它的长度为 3。

示例 3

输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0。

提示

  • 1≤text1.length≤10001 \leq text1.length \leq 10001≤text1.length≤1000
  • 1≤text2.length≤10001 \leq text2.length \leq 10001≤text2.length≤1000
  • 输入的字符串只含有小写英文字符。

模型讲解

与 LIS 模型不同的是,最长公共子序列 LCS 涉及到了两个字符数组,不再是基于单数组的问题。

根据 LIS 模型「DP 状态」设置的经验,以及「线性 DP」的核心特点,即 DP 状态沿着各个维度线性增长,我们可以如下制定「DP 状态」,f[i][j]f[i][j]f[i][j] 表示第一个串的前 iii 个字符与第二个串的前 jjj 个字符的最长公共子序列长度。

该状态的转移方程没有上一个模型这么直接,因此我们需要进行分类讨论。

假如 text1[i]≠text2[j]text1[i] \not= text2[j]text1[i]​=text2[j],即 text1[i]text1[i]text1[i] 无法与 text2[j]text2[j]text2[j] 匹配,因此 f[i][j]=max(f[i][j−1],f[i−1][j])f[i][j]=max(f[i][j-1],f[i-1][j])f[i][j]=max(f[i][j−1],f[i−1][j])。

假如 text1[i]==text2[j]text1[i] == text2[j]text1[i]==text2[j],则 text1[i]text1[i]text1[i] 可以与 text2[j]text2[j]text2[j] 匹配,因此我们可以增加一种转移方式,f[i][j]=f[i−1][j−1]+1f[i][j] = f[i-1][j-1]+1f[i][j]=f[i−1][j−1]+1。

综合上述情况,我们可以得到最终的「DP 转移方程」:
f[i][j]={f[i−1][j]f[i][j−1]f[i−1][j−1]+1,text1[i]==text2[j]f[i][j]=\left\{ \begin{aligned} & f[i-1][j] \\ & f[i][j-1] \\ & f[i-1][j-1]+1, text1[i]==text2[j] \end{aligned} \right. f[i][j]=⎩⎪⎨⎪⎧​​f[i−1][j]f[i][j−1]f[i−1][j−1]+1,text1[i]==text2[j]​

LCS 作为最基本的双串匹配 DP 模型,其转移方式考察较为频繁,大家需要好好把握理解。该算法时间复杂度为 O(nm)O(nm)O(nm),nnn、mmm 分别为 text1text1text1、text2text2text2 串的长度。

C++ 代码实现

class Solution {public:int longestCommonSubsequence(string text1, string text2) {int n = text1.length(), m = text2.length();vector<vector<int> > f(n+1, vector<int>(m+1, 0));for(int i = 1; i <= n; i++) {for(int j = 1; j <= m; j++) {f[i][j] = max(f[i-1][j], f[i][j-1]);if(text1[i-1] == text2[j-1])f[i][j] = max(f[i][j], f[i-1][j-1]+1);}}return f[n][m];}
};

120. 三角形最小路径和

题目描述

给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。

相邻的结点 在这里指的是「下标」与「上一层结点下标」相同或者等于「上一层结点下标 + 1 」的两个结点。

例如,给定三角形:

[[2],[3,4],[6,5,7],[4,1,8,3]
]

自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

说明

如果你可以只使用 O(n)O(n)O(n) 的额外空间(nnn 为三角形的总行数)来解决这个问题,那么你的算法会很加分。

模型讲解

该模型即为「线性 DP」基础模型之一:数字三角形,即最常见的二维坐标系「DP 模型」。

考虑到「线性 DP」中 DP 状态沿着各个维度线性增长的这一特点,以及本题所求的从上到下的最小路径和,不难得出状态 f[i][j]f[i][j]f[i][j] 表示从顶点出发到达第 iii 行第 jjj 列这个点时的最小路径和。

由于题目中限制 (i,j)(i,j)(i,j) 只能由 (i−1,j−1)(i-1,j-1)(i−1,j−1) 和 (i−1,j)(i-1,j)(i−1,j) 两个点到达,因此我们可以得到如下「DP 转移方程」:
f[i][j]=triangle[i][j]+min(f[i−1][j−1],f[i−1][j])f[i][j]=triangle[i][j]+min(f[i-1][j-1],f[i-1][j]) f[i][j]=triangle[i][j]+min(f[i−1][j−1],f[i−1][j])

书写代码时需要注意边界的处理,如对于特定的 (i,j)(i,j)(i,j) 来说,数字三角形中不存在 (i−1,j−1)(i-1,j-1)(i−1,j−1) 或 (i−1,j)(i-1,j)(i−1,j)。

该模型的重要意义在于告诉了我们二维坐标系中也是可以进行「线性 DP」的,而且我们可以直接根据坐标点设置「DP 状态」。

C++ 代码实现

class Solution {public:int minimumTotal(vector<vector<int>>& triangle) {int n = triangle.size(), ans = 1e9;vector<vector<int> > f(n+1, vector<int>(n+1, 0));for(int i = 0; i < n; i++) {for(int j = 0; j < triangle[i].size(); j++) {if(j == triangle[i].size()-1)f[i+1][j+1] = triangle[i][j] + f[i][j];else if(j == 0)f[i+1][j+1] = triangle[i][j] + f[i][j+1];elsef[i+1][j+1] = triangle[i][j] + min(f[i][j+1], f[i][j]);if(i == n-1)ans = min(ans, f[i+1][j+1]);}}return ans;}
};

滚动数组优化

上述「DP 转移方程」的时间复杂度为 O(n2)O(n^2)O(n2),空间复杂度也为 O(n2)O(n^2)O(n2),但根据题目中的提示,本题是可以优化至 O(n)O(n)O(n) 空间复杂度的。

这种优化方法称为「滚动数组优化」,在「DP 问题」中非常常见,主要适用于 f[i][j]f[i][j]f[i][j] 仅由 f[i−1][k]f[i-1][k]f[i−1][k] 转移而来的情况。

例如在本题中,「DP 转移方程」如下:
f[i][j]=triangle[i][j]+min(f[i−1][j−1],f[i−1][j])f[i][j]=triangle[i][j]+min(f[i-1][j-1],f[i-1][j]) f[i][j]=triangle[i][j]+min(f[i−1][j−1],f[i−1][j])

不难发现,f[i][j]f[i][j]f[i][j] 仅由 f[i−1][j−1]f[i-1][j-1]f[i−1][j−1] 和 f[i−1][j]f[i-1][j]f[i−1][j] 所决定,因此对于一个固定的 iii,我们可以从 nnn 到 111 倒序枚举 jjj,由此可以优化至如下转移方程:
f[j]=triangle[i][j]+min(f[j−1],f[j])f[j]=triangle[i][j]+min(f[j-1],f[j]) f[j]=triangle[i][j]+min(f[j−1],f[j])

代码形式如下所示:(未进行边界处理,仅作为转移示例)

for(int i = 0; i < n; i++) {for(int j = triangle[i].size()-1; j >= 0; j--) {f[j] = triangle[i][j] + min(f[j-1], f[j]);}
}

在上述代码中我们可以发现,在更新 f[j]f[j]f[j] 时,f[j−1]f[j-1]f[j−1] 与 f[j]f[j]f[j] 并未更新,此时的 f[j−1]f[j-1]f[j−1] 与 f[j]f[j]f[j] 其实是 f[i−1][j−1]f[i-1][j-1]f[i−1][j−1] 和 f[i−1][j]f[i-1][j]f[i−1][j] 的值,因此这种转移方式正确。

经过滚动数组优化后,该算法的空间复杂度为 O(n)O(n)O(n),时间复杂度不变,仍为 O(n2)O(n^2)O(n2)。

滚动数组完整代码

class Solution {public:int minimumTotal(vector<vector<int>>& triangle) {int n = triangle.size(), ans = 1e9;vector<int> f(n+1, 0);for(int i = 0; i < n; i++) {for(int j = triangle[i].size()-1; j >= 0; j--) {if(j == triangle[i].size()-1)f[j+1] = triangle[i][j] + f[j];else if(j == 0)f[j+1] = triangle[i][j] + f[j+1];elsef[j+1] = triangle[i][j] + min(f[j+1], f[j]);if(i == n-1)ans = min(ans, f[j+1]);}}return ans;}
};

习题练习

了解完三个常见的「线性 DP」模型后,我们来进行适当的习题练习。对于下述习题,大家需要仔细关注三点:

  1. 如何识别这是一道「线性 DP」问题
  2. 「DP 状态」是如何设置的
  3. 如何根据「DP 状态」得到「DP 转移方程」

198. 打家劫舍

题目描述

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你「不触动警报装置的情况下」,一夜之内能够偷窃到的最高金额。

示例 1

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。

示例 2

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。偷窃到的最高金额 = 2 + 9 + 1 = 12 。

提示

  • 0≤nums.length≤1000 \leq nums.length \leq 1000≤nums.length≤100
  • 0≤nums[i]≤4000 \leq nums[i] \leq 4000≤nums[i]≤400

解题思路

首先我们简要概括一下题意,即不能同时偷窃相邻两间房屋,求偷窃的最大金额。

不难发现,偷窃的过程是线性增长的,即从左到右,沿街依次偷窃,非常符合「线性 DP」的特征,因此我们可以令 f[i]f[i]f[i] 表示前 iii 间房屋能偷窃到的最高金额,由此得到如下「DP 转移方程」:

f[i]=max(f[i−1],nums[i]+f[i−2])f[i] = max(f[i-1], nums[i]+f[i-2]) f[i]=max(f[i−1],nums[i]+f[i−2])

其中 nums[i]nums[i]nums[i] 表示第 iii 间房屋的金额。

回顾一下之前的最长上升子序列(LIS),我们令 f[i]f[i]f[i] 表示以第 iii 个数为结尾的最长上升子序列的最大长度,因为最长上升子序列在转移时我们需要知道最后一个数的大小。

仿照 LIS 的「DP 状态」,我们也可以令 f[i]f[i]f[i] 表示前 iii 间房屋能偷窃到的最高金额,且第 iii 间房屋被偷窃。这样的「DP 状态」也是可以解决本题的,但需要修改「DP 转移方程」,大家可以自行思考并进行尝试。

C++ 代码实现

class Solution {public:int rob(vector<int>& nums) {int n = nums.size();if(n == 0) return 0;vector<int> f(n, 0);for(int i = 0; i < n; i++) {f[i] = nums[i];if(i >= 2) f[i] = max(f[i], f[i-2]+nums[i]);if(i >= 1) f[i] = max(f[i], f[i-1]);}return f[n-1];}
};

354. 俄罗斯套娃信封问题

题目描述

给定一些标记了宽度和高度的信封,宽度和高度以整数对形式 (w, h) 出现。当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。

请计算最多能有多少个信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。

说明

不允许旋转信封。

示例

输入: envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出: 3
解释: 最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。

解题思路

简要概括题意,求一组二维上升子序列 p1,p2,...,pmp1,p2,...,pmp1,p2,...,pm,同时满足:
wp1<wp2<...<wpmhp1<hp2<...<hpmw_{p1}<w_{p2}<...<w_{pm} \\ h_{p1}<h_{p2}<...<h_{pm} \\ wp1​<wp2​<...<wpm​hp1​<hp2​<...<hpm​

在之前的最长上升子序列(LIS)问题中,我们令 f[i]f[i]f[i] 表示以第 iii 个数为结尾的最长上升子序列的最大长度,即该「DP 状态」只能作用于一维 LIS。

而本题为二维 LIS,若令 f[i][j]f[i][j]f[i][j] 表示以 w=i,h=jw=i,h=jw=i,h=j 的信封为结尾的最长上升子序列显然不太合适,因此我们需要先控制一维,然后在另一维上进行「DP 转移」。

先控制一维,使得「DP 转移」时满足 i<ji < ji<j,则 wi≤wjw_i\leq w_jwi​≤wj​,因此我们可以先对于信封进行排序,www 为第一关键字,hhh 为第二关键字,排完序后再进行初始的一维 LIS「DP 转移」。

因此我们排完序后,令 f[i]f[i]f[i] 表示以第 (wi,hi)(w_i,h_i)(wi​,hi​) 为结尾的最长上升子序列,「DP 转移方程」如下:
f[i]=max(1,f[j]+1),j<i,hj<hi,wj<wif[i]=max(1,f[j]+1), j<i, h_j<h_i ,w_j<w_i f[i]=max(1,f[j]+1),j<i,hj​<hi​,wj​<wi​

由此我们将本问题转化成了基础的 LIS 问题,具体代码如下所示。

C++ 代码实现

class Solution {public:int maxEnvelopes(vector<vector<int>>& envelopes) {sort(envelopes.begin(), envelopes.end());int n = envelopes.size(), ans = 0;vector<int> f(n, 0);for(int i = 0; i < n; i++) {int tmp = 0;for(int j = 0; j < i; j++) {if(envelopes[j][1] < envelopes[i][1] && envelopes[j][0] < envelopes[i][0])tmp = max(tmp, f[j]);}f[i] = tmp + 1;ans = max(f[i], ans);}return ans;}
};

72. 编辑距离

题目描述

给你两个单词「word1」和「word2」,请你计算出将「word1」转换成「word2」所使用的最少操作数。

你可以对一个单词进行如下三种操作:

  1. 插入一个字符
  2. 删除一个字符
  3. 替换一个字符

示例 1

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')

示例 2

输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

解题思路

简要概括题意,使用最少的操作使得「word1」与「word2」相同。很明显所要进行的操作是从左至右线性增长的,不难联想到最长公共子序列(LCS),因此我们令 f[i][j]f[i][j]f[i][j] 表示最少的操作使得 「word1」的前 iii 个字符与「word2」的前 jjj 个字符相同。

与 LCS 的思考过程一致,假如 word1[i]≠word2[j]word1[i]\not= word2[j]word1[i]​=word2[j],则必定涉及删除或增加:
f[i][j]=min(f[i−1][j],f[i][j−1])+1f[i][j]=min(f[i-1][j],f[i][j-1])+1 f[i][j]=min(f[i−1][j],f[i][j−1])+1

假如 word1[i]==word2[j]word1[i]==word2[j]word1[i]==word2[j],则需要在原先基础上增加一种转移方式:
f[i][j]=min(f[i−1][j−1],min(f[i−1][j],f[i][j−1])+1)f[i][j]=min(f[i-1][j-1], min(f[i-1][j],f[i][j-1])+1) f[i][j]=min(f[i−1][j−1],min(f[i−1][j],f[i][j−1])+1)

最后我们需要控制一下边界:
f[i][0]=i,f[0][j]=j,1≤i≤word1.length(),1≤j≤word2.length()f[i][0]=i,f[0][j]=j,1\leq i\leq word1.length(),1\leq j\leq word2.length() f[i][0]=i,f[0][j]=j,1≤i≤word1.length(),1≤j≤word2.length()

C++ 代码实现

class Solution {public:int minDistance(string word1, string word2) {int n = word1.length(), m = word2.length();vector<vector<int> > f(n+1, vector<int>(m+1, 0));for(int i = 1; i <= n; i++) f[i][0] = i;for(int j = 1; j <= m; j++) f[0][j] = j;for(int i = 1; i <= n; i++) {for(int j = 1; j <= m; j++) {if(word1[i-1] == word2[j-1]) f[i][j] = f[i-1][j-1];else f[i][j] = min(f[i-1][j-1]+1, min(f[i][j-1]+1, f[i-1][j]+1));}}return f[n][m];}
};

总结

上述习题练习的后两道,「俄罗斯套娃信封问题」与「编辑距离」在力扣上的难度均为困难,但做完题后不难发现其本质仍然是基础线性 DP 模型「LIS」与「LCS」的变形。事实上,大部分「线性 DP」问题(不涉及背包)都可以在最初介绍的三个基础模型「LIS」、「LCS」、「数字三角形」中找到类似的解题思路,因此大家需要熟练掌握。

为了方便大家后续查阅,我们将三个模型总结如下:

注意,上述「DP 转移方程」均未包含边界控制,大家写题时需要自行注意。

最后,希望大家在求解「线性 DP」问题时可以回忆起上述三个基础模型,参考其「DP 思想」,祝大家刷题愉快!

算法萌新如何学好动态规划(二)相关推荐

  1. random_state的值如何选_算法萌新如何学好动态规划(3)

    本文是「动态规划」系列文章的第三篇,作为 算法萌新如何学好动态规划(2) 的一个延伸.本篇文章将主要聚焦于动态规划经典模型 -- 背包问题的讲解. 背包问题属于线性 DP 模型,之所以单独拎出来讲,主 ...

  2. 22.11.25打卡 2022河南萌新联赛第(二)场:河南理工大学 AFJL

    之前漏下的打卡慢慢补 2022河南萌新联赛第(二)场:河南理工大学_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ A题 想了好一会都没什么思路, 主要是这个1e1145 ...

  3. 2022河南萌新联赛第(二)场:河南理工大学 补题题解(A、B、C、F、J、L)

    2022河南萌新联赛第(二)场:河南理工大学 A 妙手 B 宝石 C 斩龙 F 手办 J 签到 L HPU 就过了一道题,三道签到的两题都是思路对了但没写出来,只能说自己还是太菜,需要努力! 比赛地址 ...

  4. 2022河南萌新联赛第(二)场

    2022河南萌新联赛第(二)场 目录 F 手办 G 无限 J 签到 L HPU 结语 水题水题 目录 F 手办 思路:暴力是否有立方数是n的因子 #include<bits/stdc++.h&g ...

  5. 2022河南萌新联赛第(二)场 -I题

    题目描述 现在定义一种22数, 这种数各个数位上的数字之和等于其位数的二倍 , 求 1∼n中,22数的个数 x. 输入描述: 输入包含一行一个正整数 n(1≤n≤1e10). 输出描述: 输出包含一行 ...

  6. 2022河南萌新联赛第(二)场:河南理工大学(赛后总结)

    A.妙手 题目大意: 思路: 代码: B.宝石 题目大意: 思路: 代码: C.斩龙 题目大意: 思路: 代码: #include<bits/stdc++.h> using namespa ...

  7. 2022河南萌新联赛第(二)场:河南理工大学 C - 斩龙

    C - 斩龙 模拟战斗过程:先将 DragonDragonDragon 全部击败才能对 VirmVirmVirm 进行攻击 在 VirmVirmVirm 加入战斗后,当我们伤害能一下击败一只 Drag ...

  8. 2022/7/17/题解2022河南萌新联赛第(二)场:河南理工大学https://ac.nowcoder.com/acm/contest/37344

    A题:https://ac.nowcoder.com/acm/contest/37344/A 看代码比说结论要快得多 #include<bits/stdc++.h> using names ...

  9. 机器学习萌新必学的 Top10 算法

    点击上方"Datawhale",选择"星标"公众号 价值内容第一时间获取 来源: 量子位 在机器学习领域里,不存在一种万能的算法可以完美解决所有问题,尤其是像预 ...

  10. 2022河南萌新联赛

    2022河南萌新联赛第(一)场:河南工业大学 A - Alice and Bob B - 打对子 C - 割竿榄 D - 纪念品领取 E - 聚会 F - 买车 G - 热身小游戏 H - 兴奋值 I ...

最新文章

  1. 尹伊:Datawhale做的一件事
  2. 腾讯云主机安全防护(云镜卸载)--/usr/local/qcloud/YunJing/YDEyes/YDService
  3. django 完整日志配置
  4. 累加出整个范围所有的数最少还需要几个数
  5. Get Cache Info in Linux on ARMv8 64-bit Platform
  6. NIO的空轮询bug是什么?netty是如何解决NIO空轮询bug的?
  7. 计算机偏门术语,没听说过 WinXP偏门应用技巧四则
  8. 增加mysql的sortbuffer_mysql 参数调优(14)之优化filesort sort_buffer_size、innodb_sort_buffer_size...
  9. 在算法横行的时代,仍需要人类把关
  10. mysql 分页 pageindex_mysql 超1亿数据,优化分页查询
  11. php:两个文件夹递归地比较,没有的文件自动复制过去
  12. Tomcat详解(四)——Tomcat配置详解
  13. 容斥原理+简单博弈论(找个时间补充一下sg,希望我记得)
  14. 论文管理:zotero的安装和插件使用
  15. Ashen的成长,从CSDN博客开始!
  16. transform.position 绝对位置与相对位置
  17. Fedora linux 3322动态域名解析设置
  18. excel画图如何添加图表数据参考线
  19. 一文看懂中国的运营商入库认证(中国联通入库指南)
  20. 为什么 APISIX Ingress 是比 Traefik 更好的选择?

热门文章

  1. Objective-C 基础,类和对象,方法和消息,已声明的属性和存取方法,块对象,协议和范畴类,预定义类型和编码策略...
  2. 更改wordpress上传文件大小限制
  3. oracle数据数形转换db2,DB2数字类型转换成字符串类型,例:ORACLE与DB2
  4. activerecord java_GitHub - redraiment/jactiverecord: 实现自己的ORM还是有价值的
  5. delphi memo 查找字符 下行插入_VBA实践+用编程代码为PDF文档插入书签
  6. 一核一g负载均衡不能超过多少_多核程序设计(考试题)
  7. 美团校招提前批 移动端开发 一面 二面 面经
  8. 牛客网-华为机试题(python)
  9. Django(二):安装django、创建项目及目录结构说明、在pycharm中搭建
  10. oracle 主键自增函数_在 Oracle 中设置自增列