ACM学习:线性DP

本周学习了动态规划中的线性dp。大致理解了一些动态规划的思想与做题方法。

动态规划,即分阶段求最优解,每个最优解都有状态,且总体一定可以求得最优解。它与贪心有些相似,但最大的区分点在于,动态规划的核心——状态转移方程。状态转移方程,我更觉得它偏向一种数学方程,主要是对因数学参数的变化规律而确定下一阶段的的整体情况。不过对于我个人而言,自己单独从题目中总结状态转移方程还是有一些难度的。不过现在在一些积累后,能大致理解题解所给出的状态转移方程。

动态规划的解题思路也有规律:首先要判断该问题是否有最优解,若无最优解,则不能使用动态规划。若确定使用动态规划后,开始判断如何划分阶段,再根据阶段总结出各阶段的状态转移方程,同时根据题目要求要注意边界条件,然后逐步求解即可。

接下来,我就简单的以几道动态规划例题叙述一下我学到的东西:

最小正子段和:

题意:一个序列中选出一个子序列,且这个子序列的和大于0,且该子序列的和最小;

分析:首先,我们肯定这道题是有最优解的答案的,所以我们可以使用dp的方法解决,之后我们开始思考预处理与划分阶段,因为要得到最小和的子序列,因此一定要判断值的大小正负然后再进行累加,所以我们要进行排序,排序时,为了保证我们累加的数是可以构成序列的,所以我们要建造一个结构体以储存其原本的位置,这样,我们利用sort排序后即可开始利用大小累加比较和。而状态转移方程,很明显就是要在每一次的ans上做文章,根据我们每次能够连接的两个数的和的大小与原本作比较,选取最小值替代即可。

代码:

#include <bits/stdc++.h>  
using namespace std;  
const int MAX = OX7ffff;  
typedef long long ll;  
struct node  
{  
    ll val;  
    int pos;  
    node()  
    {  
        val = 0;  
        pos = 0;  
    }  
}a[MAX];  
int cmp(const node &a,const node &b)  
{  
    if(a.val == b.val)  
        return a.pos > b.pos;  
    return a.val < b.val;  
}  
int main()  
{  
    int n;  
    ll x;  
    a[0].val = a[0].pos = 0;  
    cin>>n;
    for(int i = 1; i <= n; i++)  
    {  
        cin>>x;
        a[i].val = a[i-1].val + x;  
        a[i].pos = i;  
    }  
    sort(a, a+1+n, cmp);  
    ll ans = OX7ff;  
    for(int i = 1; i <= n; i++)  
    {  
        if(a[i].val > a[i-1].val && a[i].pos > a[i-1].pos)  
            ans = min(ans, a[i].val - a[i-1].val);  
    }  
    cout<<ans<<endl;;  
    return 0;  
}  

这个是我第一个尝试的代码,思路和想法与原先题解很相似,但理解很透彻就搬上来了。

魔族密码:

题意:给定一个字符串,然后挑选挑选其中单词数最多的词链选出单词数

分析:只看题的话刚开始确实有点懵,不过看了案例后会好很多,其实就是,依次给出一系列字符串,如果后面的字符是由前面的字符增加字母新生成的,则可以连接,而且注意,这个词链可以跳跃,即只要下一个的下标大于前一个的下标即可。这个阶段很好明白,每个字符串得长度即是一个阶段,而状态也能够分析得到,看是否后面得到的与前面得到的用max取即可。

代码:

#include<bits/stdc++.h>
using namespace std;
int n, f[2001], ans;
string s[2001];
int main()
{
	cin>>n;
	for (int i=1; i<=n; i++)
		cin >>s[i];
	
	for (int i=1; i<=n; i++)
	{
		f[i] = 1;
		for (int j=1; j<i; j++)
			if (s[j]==s[i].substr(0,s[j].size())) 
				f[i] = max(f[j]+1, f[i]);
		ans = max(f[i], ans);
	}
	cout<<ans<<endl;
	return 0;
}

这个代码用到了我当初总结过的一个string 里的函数,substr()(即字符串截取函数),用这个截取是最方便的。(也算是当初总结心得的便利),这样依次比较之后直接就可以得最大数。这里i和j的位置也是很重要的一个点,怎样用后面直接得到前面的。其实整体思路很简单,且状态转移方程很朴素易懂。(也是我尝试A题的原因)是一个很好提升理解的题目。

小A点菜:

题意:有N元,M个菜,每个菜有各自的价格,要求花完钱,并且,问有几种方法。

分析:实际是一个背包问题,问背与不背,但要求是保证钱花完,所以我们要保证,在选择第i个菜品时,它的价格与前面的和相加为N,所以这样就能够将第i个菜 的选择状态与前面的已选择状态联系起来,得到f[j]+=f[j-a[i]]这个状态转移方程,当然,如果不选,也是方案的一种,所以也要考虑到,即f[i][j]+=f[i-1][j];然后精简流程即可。

这道题有一定难度,我大概看懂了几个DP题解,他们虽然具体过程不同,但状态方程的选择却出奇一致,这也表明,动态规划的核心就是状态转移方程,而且背包问题的实质也是动态规划的一种。根据之前的经验,或许以后我可以尝试把搜索,并查集等知识与dp联系起来。

总结

本周dp其实有一定的难度,相较上周的需要更多的思考,而且本周看的题还未与之前的一些知识结合起来,这说明,题目难度的提升还是有空间的。我更要着重注意一下。而且,本周学习的是线性dp,之后的区间dp等问题应该会产生更多分化问题,因此,要对dp的注重点和细节更注意。本周理解的比较简单,等下周再继续吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值