ACM第四周
最近的动态规划一直没怎么做题,因为还在看课件上的例题,动态规划的例题还真挺多的。实话说,动态规划的题确实比贪心的题感觉稍微难一点,当然了,也可能是因为刚接触动态规划,跟动态规划还不够熟,就感觉放不开手脚。
主要知识
先说一下学习动态规划后,主要对于动态规划的认识和重点知识以及对于我来说的一些难点易错点。
首先,对于动态规划的一些比较主要的词汇:
阶段、状态、决策、状态转移方程。
动态规划是分阶段求最优值的方法,和贪心算法不同。贪心是一个贪心规律一直运行下去,而动态规划是分阶段,将一个问题分为若干个小问题,利用小问题的值来解决大问题。动态规划每一阶段都会有状态变量,除了找出解题的方法,还需要写出每一阶段的状态方程。然而,我有时候真的是找不出状态方程,这个真的是有点难找,有时候自己写的状态方程就感觉模棱两可,然后有很多小错误,我感觉这应该是动态规划最难的了吧。
Jumping Cows
题目大意:
农夫约翰的牛想跳上月亮,就像牛在他们最喜欢的童谣。不幸的是,牛不能跳。
当地的巫医把P(1<=P<=150,000)药水混合在一起,以帮助牛寻求跳跃。这些药剂必须按照它们创建的顺序正确地进行管理,尽管有些药剂可能会被跳过。
每种药剂都有一种“力量”(1<=强度<=500),可以增强奶牛的跳跃能力。在奇数的时间步骤中服用药剂会增加牛的跳跃;在偶数的时间步骤中服用药剂会减少跳跃。在服用任何药剂之前,牛的跳跃能力当然是0。
任何药剂都不能服用两次,一旦牛开始服用药剂,必须在每一时间步骤中服用一种药剂,从时间1开始。每轮可以跳过一个或多个药剂。
确定要服用哪种药剂才能跳得最高。
解析题意:
dp[i][0] 表示 当拿到第i个药时,之前已经拿了奇数个药的最大弹跳力。
dp[i][1] 表示 当拿到第i个药时,之前已经拿了偶数个药的最大弹跳力。
状态方程就是:
dp[i][0] = max(dp[i-1][0], dp[i-1][1]- v[i]);
dp[i][1] = max(dp[i-1][1], dp[i-1][0]+v[i]);
程序代码:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int p[151000][2];
int maxd(int a,int b)
{
return a<b?b:a;
}
int main()
{
int n,i,t;
while (scanf("%d",&n) != EOF)
{
p[0][0]=0;
p[0][1]=0;
for (i=1; i<=n; i++)
{
scanf("%d",&t);
p[i][0]=maxd(p[i-1][0],p[i-1][1]-t);
p[i][1]=maxd(p[i-1][1],p[i-1][0]+t);
}
printf("%d\n",maxd(p[n][0],p[n][1]));
}
}
这个是我在改之后的代码。当然了,在刚接触时写的代码就有点惨不忍睹了,当时还没有真正认识到贪心算法的精髓,就感觉还是之前的那种感觉,所以,错的一塌糊涂。
#include<iostream>
using namespace std;
int main()
{ int p;
cin>>p;
int q[150000];
int sum=0;
for(int i=1; i<=p; i++)
{ cin>>q[i];
if(i%2)
sum+=q[i];
}
cout<<sum;
return 0;
}
这个就是没有分阶段的后果,刚开始做,没有这个意识,当然也是因为题目没有分析明白。
最长上升子序列
这是我们介绍动态规划时用的第一个例子。印象是最深刻的。
给出一序列b[N],求最长的上升子序列。
题目解析:从2到n开始遍历,求出以i结尾的最长上升子序列,所以,用dp[i]代表以i结尾的最长子序列,
dp[1]=b[i],
dp[i]=max(dp[k],1<k<=i),
最后遍历一下,看看以哪个结尾最长即可。
代码如下:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int maxn=1e5;
long long temp,ans,k,m,n,i,j,h,f,a[maxn],b[maxn],dp[maxn],minn,ma;
int main()
{
cin>>n;
for( i = 1; i <= n; i ++ )
cin>>b[i];
dp[1] = 1;
for( i = 2; i <= n; i ++ )
{
int temp = 0;
for( j = 1; j < i; j ++ )
{
if( b[i] > b[j] )
{
if( temp < dp[j] )
temp = dp[j];
}
}
dp[i] = temp + 1;
}
int sum = -1;
for( i = 1; i <= n; i ++ )
if( sum < dp[i])
sum = dp[i];
cout<<sum<<endl;
return 0;
}
这个在老师刚讲的时候,还真的是有点搞不太明白,其实还是在老师慢慢往下讲的过程中,才慢慢接受的。
最大子段和
给一个序列,求连续子段的最大和,若全是负数,则全为零。
解题思路:这个题和最长上升子序列还是有比较类似的一个题。
先用dp[i]来储存以i结尾的最大字段和,然后遍历即可。
状态方程:
dp[1]=max(a[i],0)
dp[i]=max(dp[i-1]+a[i])
这个题,我没有把整个的代码写出来,唉,太难了,等做题的时候再写吧。fighting !fighting!
填写一个代码块:
dp[1]=max(a[i],0)
dp[i]=max(dp[i-1]+a[i])
最大连续自序列
这个是根本无异于上面两个题目,这是vjudge的T题,我改的一个代码如下:
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
int k[10010];
int main()
{
int n;
while(scanf("%d",&n)&&n)
{
int flag=0;
for(int i=0; i<n; i++)
{
scanf("%d",&k[i]);
if(k[i]>=0)
flag=1;
}
if(!flag)
{
printf("0 %d %d\n",k[0],k[n-1]);
continue;
}
int f=-1,s,mi=-1111111;
for(int i=0; i<n; i++)
{
if(k[i]>0)
{
f=i;
break;
}
else if(k[i]>mi)
{
mi=k[i];
s=i;
}
}
if(f==-1)
{
printf("%d %d %d\n",k[s],k[s],k[s]);
continue;
}
int add=k[f],sum=k[f],l=f,r=f,sl=f,sr=f;
for(f++; f<n; f++)
{
add+=k[f];
if(add>sum)
{
sum=add;
l=sl;
r=f;
}
if(add<0)
{
add=0;
sl=f+1;
}
}
printf("%d %d %d\n",sum,k[l],k[r]);
}
return 0;
}
其实后边真的还有好多例题,也还有很多没做完的vjudge题目。
哦,天呐!
总结:
应该是两周了,学动态规划已经两周的时间了。老师上课也一直讲一些例题,这对我们理解动态规划,解动态规划的题目还是有很大帮助的。
对于我来说,在动态规划的解题中,最难的还是找状态转移方程,因为这个要考虑到各个阶段的状态,还是有点难的。然后,对于边界的确定,我还处在一个懵懵的状态,就感觉,做题,或者是看例题的时候,有点不知所措,有时候找的不够准确,不够完整,这样就不能完整的做出题目,这还是一个很大的漏洞的,还得继续看题做题。
最后我想说的是,动态规划一般会包含最少得两重循环,这就非常容易超时,我的代码能力有非常弱,所以,在我做题中,这也是一个致命的弱点。
还是要找技巧减少运行的时间的,就要多加练习了!