首先可以依据数据范围来看大概是需要几维数组,如果他的数据范围特别小,那么意味你可能需要的数组的维度也会特别多,可能三四维都是有可能的。
最终的执行次数差不多是1e7 or 1e8之类的,你可以大概除一下看看你的复杂度大概应该是多少。
还有一个很重要的点,就是边界,边界就只需要看起点就可以了。
初始化就只需要一开始初始化就好了。
状态表示
然后呢,很重要的一个点就是,其实很多时候你的dp就是之前做过的题目的模型改变过来的。就像是组合模型就是dp,如果你之前没有学过背包这个经典组合模型,那你就是几乎完全想不出来你应怎么样进行状态表示。所有的题目都是由其他题目改写过来的,有一些题目被称之为模型,模板,是一开始的样子,如果你这些题目没有刷到的话,没有理解,没有熟练的话。那你遇到它的变形,就是几乎没有希望做出来了。
脑子里有题型,才能做题,脑子里没题型,那就是地狱难度,创造的难度有多大你懂吧,以你一己之力去对抗kmp,dp,dfs,dijkstra,并查集,的发明这,是真的会被秒成渣,当然,要是够厉害,每一题都把暴力写明白也一样很强。
所以就没啥办法,多刷题,刷到学过模型的题目就练习一下自己的举一反三能力,刷到没学过的模型的题目就练习一下自己的抽象总结,理解记忆能力。就是如此,没啥办法。比如说背包问题dp[i][j] 就是i代表考虑前i个,j代表限制(背包容量)。就是如此
边界(初始)很重要,所谓dp,就是一堆边界不断叠加起来变成结果,就很想一个积分的过程,堆积起一笑块,最后变成一块。
如果说求的是最大值而不是数量的话,那么可以在划分范围的时候放大一下范围。
分为两个部分:
1.所有方案的集合所表示的含义你要怎么设置
(因为后面要求的是所有子集,所以你在表示你当前的时候要在乎一下你要能表示的是这一步:最后一步的含义)
2.所求的集合的属性
- 如果是min和max那就是min和max
如果是方案和的话,那就是所有子集的值加起来就好了。
状态计算
考虑最后一个不同点
状态计算其实就是状态划分,你能划分就代表你已经计算出来了。
这个状态划分的就是看看你这一步的上一步,有多少种情况能够转移到你这一步的。用数组表示出所有的上一步
就最后一个点你的决策可以不同,但是前面的所有东西决策都是相同的,你可以依据这一点来写出方程。
- 如果你的状态表示式子中出现了
i-1
这种东西的话。那你的下标就要从1开始了。
没涉及到的话,就从0开始就可以了。 - 如果下标是从1开始的话,就需要考虑一下下标为0的初始化的问题了。
- 写背包问题的时候出现了一个问题,你直接从v[i]开始枚举j了,但是这样你视不拿这种情况于何地?不拿的话,无论你v是多少你都可以不拿的啊。
- 一定要掌握正确的方法啊,以前觉得dp很难,现在好好学了系统的方法之后就会发现,dp真的一点也不难,真的甚至比贪心简单。
心得
就,其实你必须得清楚一个算法的核心是上面,就只有这个核心是重要的其他的东西都只不过是为了达到这个最终目的而已。都不重要可以改变的。
dp的核心就是状态数组,只要你保证你f[i][j]每一个都是正确的,那就行了,其他所有的一切都不重要,只是为了达到这个目的的工具而已。
动态规划确实像一个自动的东西,你写好的一个程序,然后你喂给它一开始所需要的数据,然后它就会帮你计算出所有状态的数据,真的很妙说实话。
注意点
dp的时候,需要的是从小的地方开始,每一个地方的值都需要对,所以你能遍历到的地方你就一定要遍历到!!哪怕没有用到这个地方的值,你也要给它算出来,因为你不算这个你没有用到的东西,那你后面有用到的值就没东西凑出来了。
- 上面的不对,就是你只需要保证有用到的你赋值好就行了就行。
模型
- 背包问题dp[i][j] 就是i代表考虑前i个,j代表限制(背包容量)。
- 遇到是k的倍数这种的,转化为余数
- 就,你每一个状态的值必须都是正确的,如果说出现没有意义的,那么如果它求最大值,那你就先把没有意义的赋值为-0x3f3f3f3f
- 序列问题,子序列问题就f[i,j]就代表i到j的子序列
- 序列的问题是f[i][j] i是区间长度,j是左端点。
序列模型四件套
for(int len=1;len<=n;len++){
for(int l=0;l+len-1<n;l++){
int r = l + len - 1;
if(len == 1) {
f[l][r] = 1;
continue;
}