一.特性:
(1)最优子结构性质。即问题的最优解所包含的子问题的解也是最优的。
(2)子问题重叠性质。在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,
有些子问题会被重复计算多次,利用子问题的重叠性质,对于每个子问题只计算一次,然后将结果保存
起来,下次需要重新计算已经计算过的的问题查询结果即可。
(3)无后效性:每个状态都是过去历史的一个完整总结。
二.Dp分类
简单的Dp:
(1)递推:一般形式单一,从前往后分类枚举即可。
(2)背包:0-1背包、完全背包、分组背包、多重背包
(3)LIS:最长递增子序列,朴素的LIS是复杂度为O(n2)的算法,二分下的LIS是复杂度O(nlog2n)的算法。
(4)LCS:最长公共子序列,通常时间复杂度为O(n2)的算法
区间Dp:
枚举将区间分成左右两部分,然后求出左右区间再合并。
树形Dp:
基于在树上的数据结构,通过DFS维护从根到叶子或从叶子到根的状态转移
三.解题三部曲
第一步:确定状态
第二部:确定状态转移方程
第三步:确定编程实现方式
问题1:令 I 是一个 n 位十进制整数,如果将 I 划分为 k 段,可得到 k 个整数。这 k 个整数的乘积称为 I 的一个 k
乘积。对于给定的 n 、k 和 I,求出 I 的最大 k 乘积。
(一):确定状态:dp[ i ][ j ]: 表示前 i 个数,分成 j 段
(二):确定状态转移方程: dp[ i ][ j ] = max(dp[ i ][ j ], dp[ k ][ j - 1] * val[ k + 1][ i ]);
(三):确定编程实现方式
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn = 20; 4 char ch; 5 int dp[maxn][maxn]; 6 int num[maxn],val[maxn][maxn]; 7 int main() 8 { 9 int n,k; // n 位 k 段 10 while(~scanf("%d%d",&n,&k)) 11 { 12 memset(val,0,sizeof(val)); 13 for(int i=1;i<=n;i++) 14 { 15 cin>>ch; 16 num[i] = ch - '0'; 17 } 18 19 for(int i=1;i<=n;i++) val[i][i]=num[i]; 20 21 for(int i=1;i<=n;i++) 22 for(int j=i+1;j<=n;j++) 23 val[i][j] = val[i][j-1]*10 + val[j][j]; //表示的是存放 i 到 j 所表示的整数。 24 25 for(int i=1;i<=n;i++) dp[i][1] = val[1][i]; //只分成一段从第一位到i 位数字。 26 27 for(int i=1;i<=n;i++)//位数 28 for(int j=0;j<i;j++)//分割段数 29 for(int k=1;k<i;k++) 30 dp[i][j]=max(dp[i][j],dp[k][j-1]*val[k+1][i]); 31 32 cout<<dp[n][k]<<endl; 33 } 34 return 0; 35 }
问题二: n 个整数(可能含有负数)组成的序列 a1,a2, ... , an,求该序列字段和的最大值,当所有整数都为负数时
定义其最大子段和为0。
1 #include <bits/stdc++.h> 2 #define ll long long 3 using namespace std; 4 ll a[100000]; 5 int main() 6 { 7 int n; 8 ll sum = 0, temp = 0; 9 cin>>n; 10 for(int i = 1; i <= n; i++) 11 { 12 cin>>a[i]; 13 if(temp > 0) temp += a[i]; 14 else temp = a[i]; 15 sum = max(temp,sum); 16 } 17 cout<<sum<<endl; 18 return 0; 19 }
问题三: 找出由 n 个 数字组成的序列的最长单调递增子序列。
核心代码:
ll CalcMaxL(ll b[],int n) //计算序列最长递增子序列的长度 { ll t = 0; for(int i = 0; i < n; i++) if(b[i] > t) t = b[i]; return t; } ll DpLongestIncreasingSequence(ll a[], ll b[], int n) //数组 b[]记录 0 ~ i 为结尾的最长递增子序列 { int i, j, k; for(i = 1, b[0] = 1; i < n; i++) { for(j = 0, k = 0; j < i; j++) if((a[j] <= a[i]) && (k < b[j])) k = b[j]; b[i] = k - 1; } return CalcMaxL(b,n); }