每周学习总结:第五周。
本周学习:动态规划初步。
基本思想:
基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。与分治法最大的差别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。
难点:
难点: 从实际问题中抽象出动态规划表dp。
dp一般是一个数组,可能是一维的也可能是二维的,也可能是其他的数据结构:整个求解过程就可以用一个最优决策表来描述,最优决策表可以是一个二维表,其中行表示决策的阶段,列表示问题状态,表格需要填写的数据一般对应此问题的在某个阶段某个状态下的最优值(如最短路径,最长公共子序列,最大价值等),填表的过程就是根据递推关系,从1行1列开始,以行或者列优先的顺序,依次填写表格,最后根据整个表格的数据通过简单的取舍或者运算求得问题的最优解
动态规划的具体步骤
-
划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。
-
确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。
-
确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。
-
寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
例题1:vjudge的D题
我们可以按照上述步骤来:
1、划分阶段; :每个人可以自己买,也可以和前面的人或者后面的人一起买,所以一个人有两种选择;
2、决定状态和状态变量:dp[i]代表i个人花费的最短时间,a[i]表示第i个人花费的时间,b[i]表示第i个人和前一个人一共花费的时间。
3:确定状态转移方程:dp[i]=min(dp[i-1]+a[i],dp[i-2]+b[i]);
4、找边界:dp[1]=a[1];
#include <iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int a[3000],b[3000],dp[3000];
int main()
{
int t;
cin>>t;
while(t--)
{
memset(dp,0,sizeof(dp));
int n;
cin>>n;
for(int i =1;i<=n;i++)
{
cin>>a[i];
}
for(int i=2;i<=n;i++)
cin>>b[i];
dp[1]=a[1];
for(int i=2; i<=n;i++)
{
dp[i]=min(dp[i-1]+a[i],dp[i-2]+b[i]);
}
int h = dp[n]/3600;
int m =(dp[n]-h*3600)/60;
int s = (dp[n] - h * 3600 - m * 60);
h += 8;
if(h < 12)
printf("%02d:%02d:%02d am\n", h, m, s);
else printf("%02d:%02d:%02d pm\n", h, m, s);
}
return 0;
}
总结:对于这道题,思路并不难。比较难写的是输出格式,错了好多次,究其原因,还是敲代码能力不太行,还得多练练,
要不然一个题思路方法啥的都写出来了,就因为输出原因WA了,那多难受。
例2:
vjudge的J题
这一题 我直接用的暴力解法;(因为题中的变量限制范围不大。)
题意是求最小子段和的绝对值(字段和与0越近越好)
直接跳步:
2、dp[i] 前i 子段和;
3、 dp[i]=dp[i-1]+a[i]; 求所有的前i和
ans=min(ans,abs(dp[t]-dp[i-1])); 用两重循环前i字段和 里面的最小差;
这差不多就是答案了。(暴力!)。
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const double inf=1e9+10;
int a[500000];
int dp[100000];
int main()
{
int T,cas=1;
scanf("%d",&T);
while(T--)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
dp[i]=dp[i-1]+a[i];
int ans=inf;
for(int i=1;i<=n;i++)
for(int t=i;t<=n;t++)
{
ans=min(ans,abs(dp[t]-dp[i-1]));
}
printf("Case %d: %d\n",cas++,ans);
}
return 0;
}
总结:看题时候不要总限制在一种方法中,每个题有每个题适合的方法。多想多想,便守得云开见月明。
下周目标:
1、每天1~2题,周末2 ~ 3 题 冲!!!!
🆗