竞赛常考的知识点大总结(五)动态规划

DP问题的性质

动态规划(Dynamic Programming,DP)是指在解决动态规划问题时所依赖的一些基本特征和规律。动态规划是一种将复杂问题分解为更小子问题来解决的方法,它适用于具有重叠子问题和最优子结构性质的问题。动态规划问题通常具有以下特点:

特点:

1.最优子结构:问题的最优解包含其子问题的最优解。也就是说,一个问题的最优解可以从其子问题的最优解构造而来。

2.重叠子问题:在问题的求解过程中,相同的子问题会被多次计算。动态规划通过存储这些子问题的解来避免重复计算。

3.无后效性:一旦某个阶段的状态确定之后,它就不会再受之后阶段的决策影响。即一个阶段的状态一旦确定,就不会再改变。

4.状态转移方程:动态规划问题通常可以通过状态转移方程来描述问题的递推关系,即如何从一个或多个子问题的解来得到当前问题的解。

常见用法:

1.背包问题:如0/1背包问题、完全背包问题等。

2.最长公共子序列:如编辑距离、最长公共子序列等。

3.最短路径问题:如Floyd-Warshall算法、Dijkstra算法等。

4.计数问题:如硬币找零问题、计数问题等。

5.序列问题:如最长上升子序列、最长回文子序列等。

经典C语言例题:

题目: 使用动态规划解决0/1背包问题。

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include<string.h>// 计算最大值
int max(int a, int b) {return (a > b) ? a : b;
}
// 定义背包问题的结构体
typedef struct {int capacity; // 背包容量int* weights; // 物品重量数组int* values; // 物品价值数组int n; // 物品种类数
} Knapsack;// 创建背包问题实例
Knapsack* createKnapsack(int capacity, int* weights, int* values, int n) {Knapsack* knapsack = (Knapsack*)malloc(sizeof(Knapsack));knapsack->capacity = capacity;knapsack->weights = weights;knapsack->values = values;knapsack->n = n;return knapsack;
}// 计算最大价值
int knapsack_(Knapsack* knapsack) {int** dp = (int**)malloc((knapsack->n + 1) * sizeof(int*));for (int i = 0; i <= knapsack->n; i++) {dp[i] = (int*)malloc((knapsack->capacity + 1) * sizeof(int));memset(dp[i], 0, (knapsack->capacity + 1) * sizeof(int));}for (int i = 1; i <= knapsack->n; i++) {for (int w = 1; w <= knapsack->capacity; w++) {if (knapsack->weights[i - 1] <= w) {dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - knapsack->weights[i - 1]] + knapsack->values[i - 1]);} else {dp[i][w] = dp[i - 1][w];}}}int maxValue = dp[knapsack->n][knapsack->capacity];for (int i = 0; i <= knapsack->n; i++) {free(dp[i]);}free(dp);return maxValue;
}int main() {int capacity = 50;int weights[] = {10, 20, 30};int values[] = {60, 100, 120};int n = sizeof(weights) / sizeof(weights[0]);Knapsack* knapsack = createKnapsack(capacity, weights, values, n);printf("Maximum value in knapsack: %d\n", knapsack_(knapsack));free(knapsack->weights);free(knapsack->values);free(knapsack);return 0;
}

例题分析:

1.创建背包问题实例createKnapsack函数创建一个背包问题实例,包括背包容量、物品重量数组、物品价值数组和物品种类数。

2.计算最大价值knapsack函数使用动态规划方法计算背包问题的最大价值。它首先创建一个二维数组dp来存储子问题的解,然后通过两层循环遍历所有物品和所有可能的重量,计算每个子问题的解,并更新dp数组。

3.主函数:在main函数中,定义了一个背包问题实例,并调用knapsack函数计算最大价值,最后打印结果。

这个例题展示了如何在C语言中使用动态规划解决0/1背包问题。通过这个例子,可以更好地理解动态规划在解决背包问题中的应用,以及如何使用动态规划来高效地解决具有重叠子问题和最优子结构性质的问题。动态规划通过存储子问题的解来避免重复计算,从而提高了算法的效率。

编码方法(记忆化递归、递推)

编码方法通常指的是在编程中用于解决问题的特定技巧或策略。在动态规划(DP)问题中,编码方法主要涉及记忆化递归和递推两种技术。

记忆化递归(Memoization)

记忆化递归是一种优化递归调用的技术,它通过存储已经计算过的子问题的解来避免重复计算,从而减少计算时间。记忆化通常使用一个数组或哈希表来存储子问题的解。

特点:

1.避免重复计算:通过存储子问题的解,避免了对相同子问题的重复计算。

2.提高效率:减少了不必要的计算,提高了算法的效率。

3.空间换时间:需要额外的空间来存储子问题的解。

常见用法:

1.动态规划问题:在解决具有重叠子问题的动态规划问题时,使用记忆化递归可以显著提高效率。

2.计算斐波那契数列:使用记忆化递归可以高效地计算斐波那契数列的值。

经典C语言例题:

题目: 使用记忆化递归计算斐波那契数列的第n项。

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include<string.h>
// 计算斐波那契数列的第n项,使用记忆化递归
int fibonacci(int n, int* memo) {if (n <= 1) {return n;}// 检查是否已经计算过if (memo[n] != -1) {return memo[n];}// 计算并存储结果memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo);return memo[n];
}int main() {int n = 10;int* memo = (int*)malloc((n + 1) * sizeof(int));memset(memo, -1, (n + 1) * sizeof(int));printf("Fibonacci number at position %d is %d\n", n, fibonacci(n, memo));free(memo);return 0;
}

例题分析:

1.记忆化递归函数fibonacci函数接受一个整数n和一个整数数组memo作为参数。memo数组用于存储已经计算过的斐波那契数列的值。

2.递归计算:函数首先检查n是否小于或等于1,如果是,则直接返回n。否则,函数检查memo[n]是否已经被计算过,如果是,则直接返回memo[n]

3.计算并存储结果:如果memo[n]没有被计算过,则递归地调用fibonacci函数计算n-1n-2的斐波那契数,并将结果存储在memo[n]中。

4.主函数:在main函数中,定义了一个整数n和一个整数数组memo。调用fibonacci函数计算斐波那契数列的第n项,并打印结果。

这个例题展示了如何在C语言中使用记忆化递归来计算斐波那契数列的第n项。通过这个例子,可以更好地理解记忆化递归在解决动态规划问题中的应用,以及如何使用记忆化技术来提高算法的效率。记忆化递归通过存储子问题的解,避免了对相同子问题的重复计算,从而减少了计算时间,提高了算法的效率。

递推(Tabulation)

递推是一种自底向上的动态规划技术,它从最基础的情况开始,逐步构建出整个问题的解。递推通常使用一个数组来存储子问题的解,并通过迭代的方式计算出最终的解。

特点:

1.自底向上:从最基础的情况开始,逐步构建出整个问题的解。

2.避免递归:不使用递归调用,而是通过迭代的方式计算出最终的解。

3.空间优化:通常只需要一个一维数组来存储子问题的解。

常见用法:

1.动态规划问题:在解决具有重叠子问题的动态规划问题时,使用递推可以避免递归调用带来的额外开销。

2.计算斐波那契数列:使用递推可以高效地计算斐波那契数列的值。

经典C语言例题:

题目: 使用递推计算斐波那契数列的第n项。

示例代码:

#include <stdio.h>// 计算斐波那契数列的第n项,使用递推
int fibonacci(int n) {int fib[n + 1];fib[0] = 0;fib[1] = 1;for (int i = 2; i <= n; i++) {fib[i] = fib[i - 1] + fib[i - 2];}return fib[n];
}int main() {int n = 10;printf("Fibonacci number at position %d is %d\n", n, fibonacci(n));return 0;
}

例题分析:

1.递推函数fibonacci函数接受一个整数n作为参数,并计算斐波那契数列的第n项。

2.初始化数组:函数首先初始化一个数组fib,并设置fib[0]为0,fib[1]为1。

3.迭代计算:函数通过一个循环从i = 2开始,到i = n结束,计算fib[i]的值,并将其存储在数组中。

4.返回结果:函数最后返回fib[n]的值,即斐波那契数列的第n项。

这个例题展示了如何在C语言中使用递推技术来计算斐波那契数列的第n项。通过这个例子,可以更好地理解递推在解决动态规划问题中的应用,以及如何使用迭代的方式来计算问题的解。递推通过自底向上的方式,逐步构建出整个问题的解,避免了递归调用带来的额外开销,提高了算法的效率。

滚动数组

滚动数组(Rolling Array)是一种在动态规划问题中使用的优化技术,它用于减少存储空间的使用。滚动数组通过重用数组空间来存储不同阶段的子问题解,从而避免了为每个阶段都创建一个新数组。

特点:

1.空间优化:滚动数组通过重用数组空间来减少存储空间的使用,通常将数组大小减少到常数级别。

2.避免重复创建数组:在动态规划问题中,每个阶段的子问题解通常只依赖于前一个阶段的解,因此可以重用数组空间。

3.易于实现:滚动数组的实现通常很简单,只需要在计算下一个阶段的解时覆盖前一个阶段的解即可。

4.适用范围:滚动数组适用于那些子问题解只依赖于前一个阶段解的动态规划问题。

常见用法:

1.动态规划问题:在解决具有重叠子问题的动态规划问题时,使用滚动数组可以显著减少空间复杂度。

2.计算斐波那契数列:使用滚动数组可以高效地计算斐波那契数列的值。

经典C语言例题:

题目: 使用滚动数组计算斐波那契数列的第n项。

示例代码:

#include <stdio.h>// 计算斐波那契数列的第n项,使用滚动数组
int fibonacci(int n) {int a = 0, b = 1, c;if (n == 0) {return a;}for (int i = 2; i <= n; i++) {c = a + b;a = b;b = c;}return b;
}int main() {int n = 10;printf("Fibonacci number at position %d is %d\n", n, fibonacci(n));return 0;
}

例题分析:

1.滚动数组:在fibonacci函数中,我们只使用了三个变量abc来存储斐波那契数列的当前项、前一项和下一项的值。

2.计算斐波那契数列:函数首先初始化a为0,b为1。然后,通过一个循环从i = 2开始,到i = n结束,计算斐波那契数列的第n项。在每次循环中,我们计算下一项的值c,然后更新ab的值,使得a始终存储前一项的值,b始终存储当前项的值。

3.返回结果:函数最后返回b的值,即斐波那契数列的第n项。

这个例题展示了如何在C语言中使用滚动数组来计算斐波那契数列的第n项。通过这个例子,可以更好地理解滚动数组在解决动态规划问题中的应用,以及如何使用滚动数组来减少存储空间的使用。滚动数组通过重用数组空间来存储不同阶段的子问题解,从而避免了为每个阶段都创建一个新数组,提高了算法的空间效率。

常见线性DP问题

线性动态规划(Linear Dynamic Programming)问题是指那些状态转移只依赖于前一个或几个状态的动态规划问题。这类问题的特点是状态转移方程通常只涉及一维或二维的状态数组,因此它们的解决方案通常比多维状态的动态规划问题更简单、更直观。

特点:

1.状态转移简单:状态转移方程通常只涉及一维或二维的状态数组,使得状态转移过程简单明了。

2.空间复杂度低:由于状态转移的简单性,这类问题的空间复杂度通常较低,有时甚至可以优化到O(1)。

3.易于实现:线性动态规划问题的实现通常比较简单,容易编写和调试。

4.适用范围广:许多常见的动态规划问题都可以归类为线性动态规划问题,如最长公共子序列、最长上升子序列、最大子数组和等。

常见用法:

1.最长公共子序列:如经典的编辑距离问题。

2.最长上升子序列:如股票买卖问题。

3.最大子数组和:如最大子数组问题。

4.背包问题:如0/1背包问题和完全背包问题。

经典C语言例题:

题目: 使用线性动态规划解决最长上升子序列问题。

示例代码:

#include <stdio.h>
#include <stdlib.h>// 计算最长上升子序列的长度
int lengthOfLIS(int* nums, int numsSize) {int* dp = (int*)malloc(numsSize * sizeof(int));int maxLen = 0;for (int i = 0; i < numsSize; i++) {dp[i] = 1;for (int j = 0; j < i; j++) {if (nums[i] > nums[j] && dp[i] < dp[j] + 1) {dp[i] = dp[j] + 1;}}maxLen = (dp[i] > maxLen) ? dp[i] : maxLen;}int result = maxLen;free(dp);return result;
}// 计算最大值
int max(int a, int b) {return (a > b) ? a : b;
}int main() {int nums[] = {10, 9, 2, 5, 3, 7, 101, 18};int numsSize = sizeof(nums) / sizeof(nums[0]);printf("Length of longest increasing subsequence is: %d\n", lengthOfLIS(nums, numsSize));return 0;
}

例题分析:

1.计算最长上升子序列的长度lengthOfLIS函数接受一个整数数组nums和数组的大小numsSize作为参数,并计算最长上升子序列的长度。

2.初始化dp数组:函数首先创建一个与输入数组大小相同的dp数组,并初始化所有元素为1。

3.状态转移:函数通过两层循环遍历数组中的每个元素。对于每个元素nums[i],函数通过内层循环检查所有小于nums[i]的元素nums[j],并更新dp[i]的值,使其等于dp[j] + 1的最大值,前提是nums[i] > nums[j]

4.更新最大长度:在每次更新dp[i]后,函数检查dp[i]是否大于当前已知的最大长度maxLen,如果是,则更新maxLen

5.返回结果:函数最后返回maxLen的值,即最长上升子序列的长度。

这个例题展示了如何在C语言中使用线性动态规划解决最长上升子序列问题。通过这个例子,可以更好地理解线性动态规划在解决特定类型问题中的应用,以及如何使用线性动态规划来高效地解决问题。线性动态规划通过状态转移方程来计算问题的解,通常具有较低的空间复杂度,使得问题的解决方案更加高效和简洁。

背包问题

背包问题(Knapsack Problem)是计算机科学中的一个经典问题,属于组合优化问题。它描述的是这样一个场景:有一个背包,背包的承重有限,同时有一系列物品,每个物品都有自己的重量和价值。问题的目标是选择一些物品,使得这些物品的总重量不超过背包的承重,同时这些物品的总价值尽可能高。

特点:

1.组合优化:背包问题属于组合优化问题,它要求在有限的条件下选择最优的组合。

2.决策过程:问题的解决过程涉及到一系列的决策,即选择哪些物品放入背包。

3.重叠子问题:背包问题具有重叠子问题的特性,即在解决大问题的过程中会反复遇到相同的小问题。

4.最优子结构:背包问题具有最优子结构的特性,即问题的最优解包含其子问题的最优解。

常见用法:

1.资源分配:在资源有限的情况下,如何分配资源以最大化效益。

2.装载问题:如何装载货物以最大化运输效率。

3.时间管理:如何安排任务以最大化完成的工作量。

4.金融投资:如何分配投资以最大化收益。

经典C语言例题:

题目: 使用动态规划解决0/1背包问题。

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include<string.h>
// 定义背包问题的结构体
typedef struct {int capacity; // 背包容量int* weights; // 物品重量数组int* values; // 物品价值数组int n; // 物品种类数
} Knapsack;// 创建背包问题实例
Knapsack* createKnapsack(int capacity, int* weights, int* values, int n) {Knapsack* knapsack = (Knapsack*)malloc(sizeof(Knapsack));knapsack->capacity = capacity;knapsack->weights = weights;knapsack->values = values;knapsack->n = n;return knapsack;
}// 计算最大值
int max(int a, int b) {return (a > b) ? a : b;
}// 计算最大价值
int knapsack_(Knapsack* knapsack) {int** dp = (int**)malloc((knapsack->n + 1) * sizeof(int*));for (int i = 0; i <= knapsack->n; i++) {dp[i] = (int*)malloc((knapsack->capacity + 1) * sizeof(int));memset(dp[i], 0, (knapsack->capacity + 1) * sizeof(int));}for (int i = 1; i <= knapsack->n; i++) {for (int w = 1; w <= knapsack->capacity; w++) {if (knapsack->weights[i - 1] <= w) {dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - knapsack->weights[i - 1]] + knapsack->values[i - 1]);} else {dp[i][w] = dp[i - 1][w];}}}int maxValue = dp[knapsack->n][knapsack->capacity];for (int i = 0; i <= knapsack->n; i++) {free(dp[i]);}free(dp);return maxValue;
}int main() {int capacity = 50;int weights[] = {10, 20, 30};int values[] = {60, 100, 120};int n = sizeof(weights) / sizeof(weights[0]);Knapsack* knapsack = createKnapsack(capacity, weights, values, n);printf("Maximum value in knapsack: %d\n", knapsack_(knapsack));free(knapsack->weights);free(knapsack->values);free(knapsack);return 0;
}

例题分析:

1.创建背包问题实例createKnapsack函数创建一个背包问题实例,包括背包容量、物品重量数组、物品价值数组和物品种类数。

2.计算最大价值knapsack函数使用动态规划方法计算背包问题的最大价值。它首先创建一个二维数组dp来存储子问题的解,然后通过两层循环遍历所有物品和所有可能的重量,计算每个子问题的解,并更新dp数组。

3.主函数:在main函数中,定义了一个背包问题实例,并调用knapsack函数计算最大价值,最后打印结果。

这个例题展示了如何在C语言中使用动态规划解决0/1背包问题。通过这个例子,可以更好地理解动态规划在解决背包问题中的应用,以及如何使用动态规划来高效地解决具有重叠子问题和最优子结构性质的问题。动态规划通过存储子问题的解来避免重复计算,从而提高了算法的效率。

最长公共子序列(LCS)

最长公共子序列(Longest Common Subsequence,LCS)问题是计算机科学中的一个经典问题,属于动态规划领域。它描述的是这样一个场景:有两个序列X和Y,找出一个最长的子序列,这个子序列在X和Y中都出现过,且在X和Y中的相对顺序与子序列中的相对顺序相同。

特点:

1.子序列:LCS问题寻找的是两个序列的子序列,而不是子串。子序列不要求连续,而子串要求连续。

2.相对顺序:子序列中的元素在原序列中的相对顺序必须保持不变。

3.最优子结构:LCS问题具有最优子结构的特性,即问题的最优解包含其子问题的最优解。

4.重叠子问题:LCS问题具有重叠子问题的特性,即在解决大问题的过程中会反复遇到相同的小问题。

常见用法:

1.生物信息学:在生物信息学中,LCS可以用于比较DNA序列或蛋白质序列。

2.版本控制系统:在版本控制系统中,LCS可以用来比较不同版本的文件。

3.文本编辑:在文本编辑器中,LCS可以用来实现自动完成和代码补全功能。

4.数据压缩:在数据压缩中,LCS可以用来找出重复的子序列,从而实现压缩。

经典C语言例题:

题目: 使用动态规划解决最长公共子序列问题。

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include<string.h>
// 计算最大值
int max(int a, int b) {return (a > b) ? a : b;
}
// 计算最长公共子序列的长度
int longestCommonSubsequence(char* text1, char* text2) {int len1 = strlen(text1);int len2 = strlen(text2);int** dp = (int**)malloc((len1 + 1) * sizeof(int*));for (int i = 0; i <= len1; i++) {dp[i] = (int*)malloc((len2 + 1) * sizeof(int));memset(dp[i], 0, (len2 + 1) * sizeof(int));}for (int i = 1; i <= len1; i++) {for (int j = 1; j <= len2; j++) {if (text1[i - 1] == text2[j - 1]) {dp[i][j] = dp[i - 1][j - 1] + 1;} else {dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);}}}int result = dp[len1][len2];for (int i = 0; i <= len1; i++) {free(dp[i]);}free(dp);return result;
}int main() {char text1[] = "AGGTAB";char text2[] = "GXTXAYB";printf("Length of LCS is: %d\n", longestCommonSubsequence(text1, text2));return 0;
}

例题分析:

1.初始化dp数组longestCommonSubsequence函数首先创建一个二维数组dp,其大小为两个字符串长度加1,用于存储子问题的解。

2.状态转移:函数通过两层循环遍历两个字符串的每个字符。对于每个字符对text1[i - 1]text2[j - 1],如果它们相等,则dp[i][j]的值为dp[i - 1][j - 1] + 1;如果不相等,则dp[i][j]的值为max(dp[i - 1][j], dp[i][j - 1])

3.返回结果:函数最后返回dp[len1][len2]的值,即两个字符串的最长公共子序列的长度。

这个例题展示了如何在C语言中使用动态规划解决最长公共子序列问题。通过这个例子,可以更好地理解动态规划在解决LCS问题中的应用,以及如何使用动态规划来高效地解决问题。动态规划通过状态转移方程来计算问题的解,通常具有较低的空间复杂度,使得问题的解决方案更加高效和简洁。

最长递增子序列(LIS)

最长递增子序列(Longest Increasing Subsequence,LIS)问题是计算机科学中的一个经典问题,属于动态规划领域。它描述的是这样一个场景:给定一个序列,找出一个最长的子序列,这个子序列中的元素是严格递增的。

特点:

1.递增子序列:LIS问题寻找的是一个递增的子序列,即子序列中的每个元素都比前一个元素大。

2.最优子结构:LIS问题具有最优子结构的特性,即问题的最优解包含其子问题的最优解。

3.重叠子问题:LIS问题具有重叠子问题的特性,即在解决大问题的过程中会反复遇到相同的小问题。

4.动态规划解法:LIS问题通常使用动态规划来解决,通过维护一个数组来存储每个位置的最长递增子序列的长度。

常见用法:

1.生物信息学:在生物信息学中,LIS可以用于分析DNA序列或蛋白质序列中的递增模式。

2.金融分析:在金融分析中,LIS可以用于识别股票价格的递增趋势。

3.数据压缩:在数据压缩中,LIS可以用于找出重复的递增模式,从而实现压缩。

4.路径规划:在路径规划中,LIS可以用于找出最短的递增路径。

经典C语言例题:

题目: 使用动态规划解决最长递增子序列问题。

示例代码:

#include <stdio.h>
#include <stdlib.h>// 计算最长递增子序列的长度
int lengthOfLIS(int* nums, int numsSize) {int* dp = (int*)malloc(numsSize * sizeof(int));int maxLen = 0;for (int i = 0; i < numsSize; i++) {dp[i] = 1;for (int j = 0; j < i; j++) {if (nums[i] > nums[j] && dp[i] < dp[j] + 1) {dp[i] = dp[j] + 1;}}maxLen = (dp[i] > maxLen) ? dp[i] : maxLen;}int result = maxLen;free(dp);return result;
}// 计算最大值
int max(int a, int b) {return (a > b) ? a : b;
}int main() {int nums[] = {10, 9, 2, 5, 3, 7, 101, 18};int numsSize = sizeof(nums) / sizeof(nums[0]);printf("Length of longest increasing subsequence is: %d\n", lengthOfLIS(nums, numsSize));return 0;
}

例题分析:

1.初始化dp数组lengthOfLIS函数首先创建一个与输入数组大小相同的dp数组,并初始化所有元素为1。

2.状态转移:函数通过两层循环遍历数组中的每个元素。对于每个元素nums[i],函数通过内层循环检查所有小于nums[i]的元素nums[j],并更新dp[i]的值,使其等于dp[j] + 1的最大值,前提是nums[i] > nums[j]

3.更新最大长度:在每次更新dp[i]后,函数检查dp[i]是否大于当前已知的最大长度maxLen,如果是,则更新maxLen

4.返回结果:函数最后返回maxLen的值,即最长递增子序列的长度。

这个例题展示了如何在C语言中使用动态规划解决最长递增子序列问题。通过这个例子,可以更好地理解动态规划在解决LIS问题中的应用,以及如何使用动态规划来高效地解决问题。动态规划通过状态转移方程来计算问题的解,通常具有较低的空间复杂度,使得问题的解决方案更加高效和简洁。

状态压缩DP

状态压缩动态规划(State Compression Dynamic Programming)是一种动态规划的优化技术,它用于减少动态规划中状态表示的空间复杂度。在某些动态规划问题中,状态的表示可以非常紧凑,通过使用位运算或其他技巧来压缩状态空间,从而减少所需的存储空间。

特点:

1.空间优化:状态压缩动态规划通过压缩状态空间来减少存储空间的使用,通常可以将空间复杂度从多项式级别降低到对数级别。

2.位运算:状态压缩通常涉及位运算,如按位与(&)、按位或(|)、按位异或(^)和左移(<<)、右移(>>)等。

3.易于实现:状态压缩的实现通常比较简单,只需要对状态进行适当的编码和解码。

4.适用范围:状态压缩动态规划适用于那些状态可以被压缩的问题,如子集和问题、硬币找零问题等。

常见用法:

1.子集和问题:如0/1背包问题,其中每个物品可以选择放入或不放入背包。

2.硬币找零问题:如给定面额的硬币,求最少硬币数来组成特定金额。

3.图着色问题:如使用最少的颜色给图中的顶点着色,使得相邻的顶点颜色不同。

经典C语言例题:

题目: 使用状态压缩动态规划解决子集和问题。

示例代码:

#include <stdio.h>
#include <stdlib.h>// 计算子集和问题的解
int subsetSum(int* nums, int numsSize, int sum) {int dp[1 << numsSize];memset(dp, 0, sizeof(dp));dp[0] = 1;for (int i = 0; i < (1 << numsSize); i++) {for (int j = 0; j < numsSize; j++) {if (i & (1 << j)) {dp[i] |= dp[i ^ (1 << j)];}}}return dp[(1 << numsSize) - 1] & (1 << sum);
}int main() {int nums[] = {1, 2, 3};int numsSize = sizeof(nums) / sizeof(nums[0]);int sum = 4;printf("Subset sum %d is %s\n", sum, subsetSum(nums, numsSize, sum) ? "possible" : "not possible");return 0;
}

例题分析:

1.初始化dp数组subsetSum函数首先创建一个大小为1 << numsSizedp数组,用于存储所有可能的子集的和。

2.状态转移:函数通过两层循环遍历所有子集。外层循环遍历所有可能的子集,内层循环检查当前子集中是否包含第j个元素。

3.状态压缩:在内层循环中,如果当前子集包含第j个元素,则使用按位异或运算符^来生成不包含第j个元素的子集,并将这两个子集的和进行按位或运算,以更新dp数组。

4.返回结果:函数最后检查dp[(1 << numsSize) - 1]是否包含sum,如果是,则返回1(表示可能),否则返回0(表示不可能)。

这个例题展示了如何在C语言中使用状态压缩动态规划解决子集和问题。通过这个例子,可以更好地理解状态压缩动态规划在解决特定类型问题中的应用,以及如何使用状态压缩技术来高效地解决问题。状态压缩动态规划通过压缩状态空间来减少存储空间的使用,使得问题的解决方案更加高效和简洁。

树形DP

树形动态规划(Tree Dynamic Programming)是一种动态规划的变体,它在树形结构上进行状态的定义和转移。树形动态规划通常用于解决树形结构上的优化问题,如树形背包问题、树形路径问题等。

特点:

1.树形结构:树形动态规划适用于树形结构的数据,如树形图、树形网络等。

2.状态定义:在树形动态规划中,状态的定义通常与树的节点相关,每个节点可以有一个或多个状态。

3.状态转移:状态转移依赖于树的结构,通常需要考虑父节点和子节点之间的关系。

4.递归实现:树形动态规划通常通过递归函数来实现,每个节点的状态转移依赖于其子节点的状态。

常见用法:

1.树形背包问题:在树形结构上进行背包问题的求解。

2.树形路径问题:在树形结构上求解最长路径、最小路径等问题。

3.树形决策问题:在树形结构上进行决策问题的求解,如树形博弈问题。

经典C语言例题:

题目: 使用树形动态规划解决树形背包问题。

示例代码:

#include <stdio.h>
#include <stdlib.h>// 定义树形背包问题的结构体
typedef struct TreeNode {int weight;int value;struct TreeNode* left;struct TreeNode* right;
} TreeNode;// 创建树形背包问题的节点
TreeNode* createTreeNode(int weight, int value) {TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));node->weight = weight;node->value = value;node->left = NULL;node->right = NULL;return node;
}// 计算树形背包问题的最大价值
int treeKnapsack(TreeNode* root, int capacity) {if (root == NULL) {return 0;}// 如果当前节点的重量大于背包容量,则不能选择当前节点if (root->weight > capacity) {return treeKnapsack(root->left, capacity) + treeKnapsack(root->right, capacity);} else {// 选择当前节点,计算剩余容量下的最大价值int include = root->value + treeKnapsack(root->left, capacity - root->weight) + treeKnapsack(root->right, capacity - root->weight);// 不选择当前节点,只计算左右子树的最大价值int exclude = treeKnapsack(root->left, capacity) + treeKnapsack(root->right, capacity);// 返回两者中的最大值return (include > exclude) ? include : exclude;}
}int main() {TreeNode* root = createTreeNode(10, 100);root->left = createTreeNode(20, 150);root->right = createTreeNode(30, 200);int capacity = 50;printf("Maximum value in knapsack: %d\n", treeKnapsack(root, capacity));free(root->left);free(root->right);free(root);return 0;
}

例题分析:

1.创建树形背包问题的节点createTreeNode函数创建树形背包问题的节点,并初始化节点的重量和价值。

2.计算树形背包问题的最大价值treeKnapsack函数使用递归方法计算树形背包问题的最大价值。函数首先检查当前节点是否为空,如果为空,则返回0。如果当前节点的重量大于背包容量,则不能选择当前节点,函数返回左右子树的最大价值之和。如果当前节点的重量小于或等于背包容量,则可以选择当前节点,函数计算包括当前节点在内的剩余容量下的最大价值,并与不选择当前节点的情况进行比较,返回两者中的最大值。

3.主函数:在main函数中,创建了一个树形背包问题的实例,并调用treeKnapsack函数计算最大价值,最后打印结果。

这个例题展示了如何在C语言中使用树形动态规划解决树形背包问题。通过这个例子,可以更好地理解树形动态规划在解决树形结构上的优化问题中的应用,以及如何使用递归方法来高效地解决问题。树形动态规划通过在树形结构上定义和转移状态,使得问题的解决方案更加高效和简洁。

数位DP

数位动态规划(Digit DP)是一种特殊的动态规划技术,它用于解决与数字相关的计数问题。数位动态规划通常用于计算在一定范围内满足特定条件的数字的数量,例如计算在一定范围内有多少个数字是回文数、有多少个数字满足特定的数位和条件等。

特点:

1.数位处理:数位动态规划涉及对数字的每一位进行单独处理,通常需要将数字转换为数位数组的形式。

2.状态定义:数位动态规划的状态通常与数字的数位有关,每个状态可能代表一个或多个数位的组合。

3.状态转移:状态转移依赖于数位之间的关系,如进位、借位等。

4.记忆化搜索:数位动态规划通常使用记忆化搜索技术来避免重复计算,提高效率。

常见用法:

1.计算回文数数量:在一定范围内计算有多少个回文数。

2.计算满足数位和的数字数量:在一定范围内计算有多少个数字的数位和满足特定条件。

3.计算满足特定数位模式的数字数量:在一定范围内计算有多少个数字符合特定的数位模式。

经典C语言例题:

题目: 使用数位动态规划计算在一定范围内有多少个回文数。

示例代码:

#include <stdio.h>
#include <string.h>// 计算回文数的数量
int countPalindromes(int L, int R) {int dp[10][10][10]; // dp[i][j][k]表示长度为i,最高位为j,最高位前一位为k的回文数数量memset(dp, 0, sizeof(dp));for (int i = 0; i < 10; i++) {dp[1][i][i] = 1; // 单位数的回文数数量}for (int len = 2; len <= R; len++) {for (int j = 0; j < 10; j++) {for (int k = 0; k < 10; k++) {for (int m = 0; m < 10; m++) {if (j == m) {dp[len][j][k] += dp[len - 1][k][m];} else {dp[len][j][k] += dp[len - 1][k][m] / 2;}}}}}int result = 0;for (int j = 0; j < 10; j++) {for (int k = 0; k < 10; k++) {result += dp[R - L + 1][j][k];}}return result;
}int main() {int L = 100, R = 999;printf("Number of palindromes between %d and %d is: %d\n", L, R, countPalindromes(L, R));return 0;
}

例题分析:

1.初始化dp数组countPalindromes函数首先创建一个三维数组dp,用于存储不同长度、最高位和最高位前一位的回文数数量。

2.状态转移:函数通过三层循环遍历所有可能的回文数长度、最高位和最高位前一位。对于每个状态,函数计算所有可能的前一位和当前位组合的数量,并更新dp数组。

3.计算结果:函数最后遍历所有可能的最高位和最高位前一位,将它们与长度R - L + 1组合,计算出在指定范围内的回文数数量,并返回结果。

这个例题展示了如何在C语言中使用数位动态规划计算在一定范围内有多少个回文数。通过这个例子,可以更好地理解数位动态规划在解决与数字相关的计数问题中的应用,以及如何使用数位动态规划来高效地解决问题。数位动态规划通过在数位层面上定义和转移状态,使得问题的解决方案更加高效和简洁。

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

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

相关文章

【Visual Studio】将项目下的文件夹所有文件随编译自动复制输出到运行目录

要将项目根目录下的文件夹内容输出到运行目录&#xff0c;去处理其中的子文件夹和文件&#xff0c;逐个手动设置文件属性或进行复制显然不是一个可行的方法&#xff0c;因为这既繁琐又低效&#xff0c;那有没有更加高效的方式呢 文章目录 选择文件夹修改配置文件输出文件夹 这里…

FreeRtos入门-3 信号量(计数值、二进制、互斥量、递归锁)

信号量 计数量 二进制 互斥量 递归锁 创建 xSemCalc xSemaphoreCreateCounting(10, 0);//计数最大值10&#xff0c;初始值0 xSemUart xSemaphoreCreateBinary(); xSemUART xSemaphoreCreateMutex(); xSemUART xSemaphoreCreateRecursiveMutex(); 释放 xSemaphore…

【c语言】自定义类型:枚举类型【详解】

枚举类型 枚举类型的声明 枚举顾名思义就是⼀⼀列举。 把可能的取值⼀⼀列举。 ⽐如我们现实⽣活中 ⼀周的星期⼀到星期⽇是有限的7天&#xff0c;可以⼀⼀列举 性别有&#xff1a;男、⼥、保密&#xff0c;也可以⼀⼀列举 ⽉份有12个⽉&#xff0c;也可以⼀⼀列举 三原⾊&am…

Linux集群(一)Nginx搭建

目录 一、Nginx介绍 1.什么是Nginx 2.Nginx的特点 二、Nginx配置 1.jdk的安装 1.1检查jdk版本 1.2上传并安装jdk 2.安装Tomcat 3.下载Nginx 3.1安装依赖包 ​编辑 3.2安装Nginx 3.3运行 三、Nginx中的常用命令​编辑 一、Nginx介绍 1.什么是Nginx Nginx&#xff08;…

【FAQ】HarmonyOS SDK 闭源开放能力 —Asset Store Kit

1.问题描述 使用关键资产API需要配置SystemCapability.Security.Asset&#xff0c;但不知道syscap.json文件应该配置在哪里&#xff0c;文档也没找到。 解决方案 新增关键资产等API可以直接参考开发指南里的示例代码进行调用&#xff1a; https://developer.huawei.com/cons…

Coursera上Learning Linux for LFCA Certification专项课程01:Linux Fundamentals 学习笔记

Linux Fundamentals Course Certificate 本文是 Linux Fundamentals 这门课的学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。 文章目录 Linux FundamentalsWeek 01: Linux Operating SystemLearning Objectives Specialization OverviewHistory of LinuxQuiz: Hist…

软件设计师27--规范化理论

软件设计师27--规范化理论 考点1&#xff1a;规范化理论基本概念函数依赖规范化理论--Amstrong公理体系候选键主属性与非主属性例题&#xff1a; 考点2&#xff1a;范式判断规范化理论规范化理论 - 范式例题&#xff1a; 考点3&#xff1a;范式分解保持函数依赖分解无损分解模式…

第14章 数据结构与集合源码

一 数据结构剖析 我们举一个形象的例子来理解数据结构的作用&#xff1a; 战场&#xff1a;程序运行所需的软件、硬件环境 战术和策略&#xff1a;数据结构 敌人&#xff1a;项目或模块的功能需求 指挥官&#xff1a;编写程序的程序员 士兵和装备&#xff1a;一行一行的代码 …

什么是stable diffusion?

&#x1f31f; Stable Diffusion&#xff1a;一种深度学习文本到图像生成模型 &#x1f31f; Stable Diffusion是2022年发布的深度学习文本到图像生成模型&#xff0c;主要用于根据文本的描述产生详细图像。它还可以应用于其他任务&#xff0c;如内补绘制、外补绘制&#xff0…

基于opencv的猫脸识别模型

opencv介绍 OpenCV的全称是Open Source Computer Vision Library&#xff0c;是一个跨平台的计算机视觉库。OpenCV是由英特尔公司发起并参与开发&#xff0c;以BSD许可证授权发行&#xff0c;可以在商业和研究领域中免费使用。OpenCV可用于开发实时的图像处理、计算机视觉以及…

【前端面试3+1】11 http和https有何不同及https的加密过程、数组有哪些方法及作用、tcp三次握手四次挥手、【分发饼干】

一、http和https有何不同&#xff1f;https的加密过程 1、不同&#xff1a; HTTP和HTTPS的主要区别在于安全性。HTTP是超文本传输协议&#xff0c;是一种用于传输数据的协议&#xff0c;但是传输的数据是明文的&#xff0c;容易被窃听和篡改。而HTTPS是在HTTP基础上加入了SSL/T…

【ORB-SLAM3】Ubuntu20.04 使用 RealSense D435i 运行 ORB-SLAM3 时遇到的一些 Bug

【ORB-SLAM3】使用 RealSense D435i 跑 ORB-SLAM3 时遇到的一些 Bug 1 hwmon command 0x80( 5 0 0 0 ) failed (response -7 HW not ready)2 No rule to make target /opt/ros/noetic/lib/x86_64-linux-gnu/librealsense2.so, needed by ../lib/libORB_SLAM3.so 1 hwmon comman…

力扣108. 将有序数组转换为二叉搜索树

Problem: 108. 将有序数组转换为二叉搜索树 文章目录 题目描述思路复杂度Code 题目描述 思路 根据二叉搜索树中序遍历为一个有序序列的特点得到&#xff1a; 1.定义左右下标left&#xff0c;right分别指向有序序列的头尾&#xff1b; 2.每次取出left和right的中间节点mid&…

电脑上怎么压缩图片?三个处理方法介绍

随着我们现在使用图片的地方越来越多&#xff0c;我们处理图片的情况也比较多了&#xff0c;通过压缩图片大小可以使图片文件更小&#xff0c;从而减少存储空间和带宽的使用&#xff0c;同时也可以提高加载速度和性能。良好的图片压缩可以有效地减少文件大小&#xff0c;同时保…

深入浅出 -- 系统架构之单体架构

单体架构&#xff08;Monolithic Architecture&#xff09; 单体架构的定义 单体架构&#xff08;Monolithic Architecture&#xff09;是一种传统的软件架构模式&#xff0c;将整个应用程序作为一个单一的、统一的单元进行开发、部署和扩展。在单体架构中&#xff0c;所有的功…

vue3.x专题十二 ---- vuex持久化(自动保存到本地)

在开发的过程中&#xff0c;例如用户信息等需要vuex中存储且需要本地存储&#xff0c;我们可以使用一个模块&#xff0c;设置好后&#xff0c;可以在修改state后自动触发并自动到本地存储数据&#xff1a; 1&#xff09;首先&#xff1a;我们需要安装一个vuex的插件vuex-persi…

JSP

概念&#xff1a;Java Server Pages&#xff0c;Java服务端页面 一种动态的网页技术&#xff0c;其中既可以定义HTML、JS、CSS等静态内容&#xff0c;还可以定义Java代码的动态内容 JSP HTML Java 快速入门 注&#xff1a;Tomcat中已经有了JSP的jar包&#xff0c;因此我们…

【yy讲解PostCSS是如何安装和使用】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

Coursera上托福专项课程01:TOEFL Reading and Listening Sections Skills Mastery 学习笔记

TOEFL Reading and Listening Sections Skills Mastery Course Certificate 本文是学习 https://www.coursera.org/learn/toefl-reading-listening-sections-skills-mastery 这门课的笔记&#xff0c;如有侵权&#xff0c;请联系删除。 文章目录 TOEFL Reading and Listening …

设计模式:创建者模式

定义 创建者模式&#xff08;Builder Pattern&#xff09;&#xff0c;又称建造者模式&#xff0c;是一种创建型设计模式&#xff0c;它提供了一种创建对象的最佳方式。该模式允许将一个复杂对象的构建与它的表示分离&#xff0c;这样同样的构建过程可以创建不同的表示。创建者…