本次学习:动态规划和一些小技巧
一:动态规划:
经过学习,我感觉动态规划比贪心难的多,这个动态规划的思想真的难以理解。
首先先说下,动态规划是分阶段求最优值的方法,和贪心不同,贪心是一个贪心规律一直运行下去,而动态规划是分阶段,将一个问题分为若干个小问题,利用小问题的值来解决大问题。动态规划每一阶段都会有状态变量,除了找出解题的方法,还需要写出每一阶段的状态方程。但这个状态方程真的不是很好找。
除此之外,动态规划会用到很多多重循环,这也就导致时间复杂度和空间复杂度过高,有时候可能我们用比较朴素的方法写的代码没出错,但一提交就会超时。所以我们还要对代码进行优化,所以,动态规划我感觉很难,不好理解。下面我总结了几类问题,都是基本的问题,很多题都是这些基础题延申出来的。
1:最长上升子序列
给出一序列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()
{
//freopen("in.txt","r",stdin);
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;
}
本题总结:一开始做动态规划的题,对于状态方程不太理解,以前也没有这样的思路解过题,上课的时候也只能说一个懵懂的状态。这个题只是入门,我懂得在一个题中存在分阶段求最优的时候,可以用到动态规划。知道了动态规划,我们要找到状态方程,这个是解题的关键。也是最难理解一部分。
2:最长公共子序列
给出两个字符串A,B,求他们的最长公共子序列
想法思路:首先,我们还是需要用到状态方程,我们用dp[i][j]代表A串从1到i个字符和B串从1到j个字符的最长的公共子序列。
if(a[i]==b[j])
dp[i][j]=dp[i-1][j-1]+1
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1])
也就是说就是逐渐加一遍历两个字符串,不相等的时候,就取减一个的字符串公共部分比较大的那个
本题总结:一开始就是不理解那个状态方程怎么来的,为什么这么写,随着做题的深入,我也渐渐理解了状态方程的意思,自己也尝试独自写出状态方程,虽然有时候还是看不懂,可能对动态规划还是缺一点感觉。
3:最大子段和
给一个序列,求连续子段的最大和,若全是负数,则全为零。
想法思路:
这个题和最长上升子序列差不多,一样的思路,我先用dp[i]来储存以i结尾的最大字段和,然后遍历即可。然后我就要找出状态方程了
dp[1]=max(a[i],0)
dp[i]=max(dp[i-1]+a[i])
总结:这里我学到了边界问题,就是当i=1时的dp值,我做了很多题,很多题都是确定边界,然后再用状态方程来解,这个边界条件也不是很好找。慢慢熟悉吧
4:
最大子矩阵和
想法思路:
上课讲的时候我一开始就是想着开一个二维数组,然后遍历,这样求。可能以前没有时间和空间的概念,对于这方面不太了解.然后就知道了算法优化,将每一列的值相加,将二维数组变为一个一维数组。然后按照最大子段和求即可。
本题总结:学到了时间复杂度和空间复杂度,若用朴素的方法,时间复杂度为o(N^4), 空间复杂度为o(N^3),肯定a不了,也懂得了如果能优化尽量优化代码,以前真的没优化过,认为a了就行,现在好了,动态规划很考验时间与空间。写着写着,发现动态规划居然有这么多小细节,也算提醒自己了一下。以后在做题的时候自己也注意一下。
下面则是我认为的一些进阶的问题,上面那些问题是基础。
5:
给出一个序列,你从第一步开始走,你走的下一步的数字必须比你走之前那个数字大,走完后,将走的数字相加,求最大值
想法思路:
我知道是动态规划的题,但我一开始的想法好想不是动态规划的思路,结果就错了,一开始想,就是走的下一步比我这个数字大呀,那我定义一个数组dp,若我走这一步,就赋值为1,不走则赋值为0,最后再相加就是呀,利用一个双重循环,求出i之后的比我这个数大的第一个数,然后break结束第一层循环即可,现在我也不知道错误。
错误代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int maxn=100005;
int k,m,n,i,j,h,f,a[maxn],b[maxn],dp[maxn],minn,ma;
int main()
{
//freopen("in.txt","r",stdin);
while(cin>>n)
{
int sum=0;
memset(dp,0,sizeof(dp));
if(n==0)
break;
for(i=1; i<=n; i++)
cin>>a[i];
for(int i=1; i<n; i++)
for(j=i+1; j<=n; j++)
{
if(a[i]>=a[j])
{
dp[j]=1;
break;
}
else
break;
}
for(i=1; i<=n; i++)
{
if(dp[i]==0)
sum+=a[i];
}
cout<<sum<<endl;
}
return 0;
}
然后,还是用动态规划吧,这个题还不算太难,和最大上升子序列差不多。
还是用dp[i]来储存i之前的最大值,利用一个双重循环来求。
for (int i = 1; i<= n; i++)
{
for (int j = i-1; j > 0; j--)
{
if (a[j] < a[i] && (dp[j] + a[i] > dp[i]))
{
dp[i]=dp[j]+a[i];
}
}
ans = max(ans, dp[i]);
}
怎么说呢,这个状态方程是怎么来的,我觉得是自己对dp的感觉和对题的理解,一开始我做dp时还真写不出来状态方程,毕竟以前没这样理解过题。
6:雇佣工人问题
项目经理希望确定每个月所需的工人人数。他确实知道每个月需要的工人数量最少。当他雇佣或解雇工人时,会有一些额外的费用。一旦一个工人被雇用,即使他不工作,他也会得到工资。经理知道雇佣工人、解雇工人的成本以及工人的工资。然后,经理将面对这样一个问题:他每个月将雇佣或解雇多少工人,以保持项目总成本的最低水平。求最低成本。
想法思路:
这个题我一开始看着不难,但就是想不出来思路,可能我不太会用二维的状态数组,我用d[i][j]来表示第i天雇佣j个人的最低成本。先找出这些月的最多的雇佣个数ma,从a[1]到ma开始遍历,先初始化dp[1][i],然后就是用了一个三重循环,让前一个月雇佣人数与这个月作比较,从而求出最少的雇佣人数
for(i=2; i<=k; i++)
{
for(j=a[i]; j<=ma; j++)
{
minn=999999999;
for(f=a[i-1]; f<=ma; f++)
{
if(j>f)
dp[i][j]=(j-f)*m+j*n+dp[i-1][f];
else
dp[i][j]=(f-j)*h+j*n+dp[i-1][f];
if(dp[i][j]<minn)
minn=dp[i][j];
}
dp[i][j]=minn;
}
}
最后,因为我并不知道最后一个月要雇佣几人,不知道是否成本会更低,所以,我再用一个循环来查看是否最后一个月有更低成本
minn=9999999;
for(i=a[k]; i<=ma; i++)
{
if(dp[k][i]<minn)
minn=dp[k][i];
}
二:一些小技巧:
freopen(“in.txt”,“r”,stdin)
将测试数据放在in.txt文件里,这样我在调试代码的时候就不用每次都要键盘输入了
INF=1e9
register 加快运行
for(register i=1;i>n;i++)
宏定义
例如:
#define rep(m,n,h) for(int m=n;m<=h;m++)
三:学习总结
这两周主要讲了动态规划,但我感觉动态规划真的好难啊,比贪心上了一个台阶,不但要想出思路,还要创造出状态方程,还要时间复杂度和空间复杂度,考虑边界条件。通过两周的学习,虽然做了一些题,但我自身感觉仍没入dp的门,做题的时候有时候看了很久,还是什么思路,就是dp的题和我以前做的题思考方式有所改变,看来还是多做题才是,还有,由于找不到思路,所以这次做题比贪心做题时慢多了,做出来时还经常超时,还要优化,滚动数组之类的,开一个小的数组,看看能不能减一层循环。再说下上次的贪心作业,确实有种能及格那样心态,我也有反思自己,前段时间真的是有所懈怠了,虽然这次dp真的不简单,但我要多刷题,找感觉!