一.特性:
(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);
}