2021年3月29日-4月10日ACM学习日志

本次学习:动态规划和一些小技巧

一:动态规划:

经过学习,我感觉动态规划比贪心难的多,这个动态规划的思想真的难以理解。
首先先说下,动态规划是分阶段求最优值的方法,和贪心不同,贪心是一个贪心规律一直运行下去,而动态规划是分阶段,将一个问题分为若干个小问题,利用小问题的值来解决大问题。动态规划每一阶段都会有状态变量,除了找出解题的方法,还需要写出每一阶段的状态方程。但这个状态方程真的不是很好找。
除此之外,动态规划会用到很多多重循环,这也就导致时间复杂度和空间复杂度过高,有时候可能我们用比较朴素的方法写的代码没出错,但一提交就会超时。所以我们还要对代码进行优化,所以,动态规划我感觉很难,不好理解。下面我总结了几类问题,都是基本的问题,很多题都是这些基础题延申出来的。
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真的不简单,但我要多刷题,找感觉!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值