再读《挑战程序设计竞赛》——出类拔萃(4)

贪心与动态规划

贪心

贪心策略可以通过样例去尝试,写完以后写个暴力打表比对,比对成功就可以交了,不要花功夫去证明没时间。
数论也可以贪心,有时候取一些特殊结论就可以。
蓝桥杯 最大最小公倍数
要求xyz/gcd(x,y,z)最小
如果n是奇数,最大三个数n,n-1,n-2是奇偶奇,他们一定互质。最小公倍数就是n(n-1)(n-2)
如果n是偶数,最大三个数n,n-1,n-2是偶奇偶,他们有公因数2.次大三个数是n-1,n-2,n-3是奇偶奇,他们一定互质。因此只要比较n(n-1)(n-2)/2和(n-1)(n-2)(n-3)大小即可。实际上只要n>3都是选后者比较好。

有些题目可以用Dp做,如果挖掘到更深刻的性质就是一个贪心问题。
leetcode44 通配符匹配
dp做法:
解释一下第三个式子,当p[j]==’*'我们可以用这个*匹配也可以不用这个*(因为他可以代替空字符),两种只要一种可以匹配成功就匹配成功了。
在这里插入图片描述
边界条件:(看做几种特殊的字符串匹配)
dp[0][0]=1 s空p空匹配成功
dp[i][0]=0 s非空p空一定无法匹配
dp[0][j]=如果s空而p的前j个字符都是*,就匹配成功

贪心做法:
观察模式串:abcd*e?fg*hij
只要暴力的先找出abcd,从下一个位置再找出e?fg,从下一个位置再找出hij就匹配成功了,否则不成功。
贪心关键是观察出一种关键的性质,去尝试用这个性质暴力解题。

相似题目:leetcode 10正则表达式匹配。

常见贪心策略

leetcode 376 摆动序列
对一个序列的单调性要敏感,像这种在序列的峰值或者谷值贪心的题目很多。

区间贪心
按头按尾从左到右从右到左排序。
或者依次序递推及时判断。
leetcode 435 无重叠区间
按右边界排序,选择最早结束的再安排下一个区间。
leetcode 452 用最少数量的箭引爆气球
也是按右边界排序,选择最早结束的,看他的右边界与几个区间有交集,就能穿破几个气球。把穿破了的区间跳过,继续寻找最早结束的,以此类推。

复杂的贪心题目通常会推出一个公式,对这个公式的计算结果排序进行贪心

Acwing125 耍杂技的牛

贪心例题

leetcode 45 跳跃游戏2
贪心策略:找到每步能跳到的最远的地方(对现在每个能跳到的地方进行扩展),直到扩展到终点。

leetcode 122 股票买卖的最佳时机
dp做法:
一个状态机DP。dp[i][0]表示当天手上没有股票,dp[i][1]表示当天手上有股票。
在这里插入图片描述
贪心做法:
因为不限制买卖的次数,只要a[i]>a[i-1]就在i-1买入,i抛售即可。

类似题目 leetcode188 股票买卖的最佳时机4
这道题有限制只能买卖k次,所以只能加入一维k进行dp

leetcode 134 加油站
贪心策略:要想汽车一直跑下去,则需要:
在这里插入图片描述

看起来每个位置都可以开始,实际上在断油处开始才是一种新方案。因为在断油处前,都满足gas>cost,直到断油处都会断油,证明如下:

在这里插入图片描述
所以要多运用数学推算贪心的性质哦。
leetcode 135 分发糖果
贪心策略:分发糖果时,先设给第一个小朋友一颗糖果。之后每个小朋友如果比前一个表现好,糖果数就等于前一个的糖果数加一。
如果没有前一个小朋友表现好,则在他之前的小朋友糖果数都加一。

leetcode 402移掉k位数字
都是去掉一些使得越小的在前面越好。

leetcode 321 拼接最大数
两个数组取出的数的长度x,y应当满足x+y=k
枚举每个x和y:
1.利用单调栈,可以找到从左/右遍历第一个比它小/大的元素的位置。这里我们找一个单调递减的x个的序列凑成第一个数组的取出的数,找一个单调递减的y个的序列凑成第二个数组取出的数。
2.将两个合并成最大的序列(有点像归并排序的操作方式)
3.比较每一组xy看哪个组成的数最大。

leetcode 1081 不同字符的最小子序列
只要是还有>1个某字母,某字母就可以被丢弃。
那么丢弃哪些字母呢?实际上也可以做一个类似单调栈的操作,把不单调且还有>1个字母的丢弃。

leetcode 502 IPO
每次选择最赚钱的项目,然后考虑可达项目即可。

动态规划基本分类

在这里插入图片描述

线性DP
线性Dp经典的就是数组上的dp和字符串dp。
leetcode 53 最大子序和
我们常常把关于某个位置的结论存储在dp[i]中,最后再取最佳的一个。
比如这道题,某个位置结尾的最大子序和就是
max{前一段最大子序和加上这个数作为一个子序列,自己本身作为一个子序列}

leetcode62 最小编辑距离
两个字符串对比也经常用DP解决,最小编辑距离应该是两个字符串对比最经典的题目了。(这其实算一个区间dp)
在这里插入图片描述
值得关注的是边界条件:
dp[i][0]=i 表示进行i次删除操作距离为i
dp[0][j]=j 表示进行j次插入操作距离为j

线性dp常见的还有最长上升子序列的各种变体,通常我们设定一个位置i,f[i]表示i位置最怎样怎样的一个属性,或者是串中以i开头(结尾)(开头常常是逆推,结尾是正推,所以常用的是结尾)的最怎样怎样的一个属性。

leetcode 32 最长有效括号
也是个关于串的问题,要落实到串的性质本身。
用f[i]表示第i个位置之前的合法括号长度。
在这里插入图片描述
在这里插入图片描述
答案就是dp数组的最大值。

leetcode 91 解码方法
假设一个串s[1…i]需要解码,那么如果他最后一位用A~I解码,s[1…i]解码的方案数就是s[1…i-1]解码的方案数。
如果最后两位用J~Z解码,s[1…i]解码的方案数就是s[1…i-2]解码的方案数。

背包Dp
背包DP是背板然后稍微根据题意改动即可。这里参考Acwing的提高课程讲的非常全面。
我的总结

状态机Dp
大多数以时序作为状态划分的依据。
举个例子,没有上司的舞会,这是个经典的树形DP,传递的时候有状态0:上一层没有参加舞会的人
状态1:上一层有参加舞会的人
得到这一层的状态0和状态1,这就是一个简单的状态机模型。
上面提到的买股票的问题:状态1是在这个时刻买了股票,状态0是在这个时刻没有买股票

蓝桥杯 砸手机
由于这个手机有三次砸的机会,就分为三种状态.0表示没砸过,1表示砸过一次,2表示砸过两次。

区间Dp
有点像线段树的处理方法,考虑两个区间如何叠加,以及叠加的新耗费
leetcode 5 最长回文子串
如果一个串p(i,j)是回文的,并且a[i-1]==a[j+1],那么p(i-1,j+1)就是回文的。
初始条件:1.某个字母肯定是回文的,即p(i,i)=1
2.某两个连在一起的字母,若是一样的就是回文的
p(i,i+1)=(a[i]==a[i+1])

对于一个串的问题,可以用i,j组成二维dp,也可以用i,len组成二维dp,看哪个方便用哪个。

leetcode516 最长回文子序列
用f[i][j]表示第i个字符到第j个字符的子串中,最长回文序列的长度。
在这里插入图片描述
这道题由f[i+!][j]推得f[i][j],所以i应当从后往前遍历,而j一定在i+1之后,于是j从i+1开始遍历。
边界条件f[i][i]=1

leetcode 312 戳气球
在戳破气球的过程中,两边气球的左右nums会发生改变。
模拟一下我们在一个区间(i,j)最后步骤需要进行的操作:戳爆一个气球k,获得nums[i]*nums[k]*nums[j]个积分。
这样就可以把区间一分为二(i,k)和(k,j)再继续进行dp。
边界情况:f[i][i]=nums[i-1]*nums[i]*nums[i+1]

leetcode 87 扰乱字符串
这题还是挺难的。
对于给出的两个串S和T,S拆分为子串S1和S2,T拆分为子串T1和T2,有两种情况:

  1. 交换:S1能由T2变换得到,S2能由T1变换得到。
  2. 不交换:S1能由T1变换得到,S2能由T2变换得到。

dp[i][j][k][h]表示s[i…j]能由t[k…h]变换得到。由于有j-i==h-k,所以用len表示长度,就可以把dp数组优化到3维
dp[i][k][len]
x是介于1到len-1的数只要有一个成功了就行。
dp[i][k][len]=dp[i][k][x]&&dp[i+x][k+x][len-x]
(对应不交换的情况)
or
dp[i][k+len-x][x]&&dp[i+x][k][len-x]
(对应交换的情况,有点绕,要好好理解,区间dp最难的是用有限的变量来表示你想得到的区间)

树形DP
考虑左右子树怎么归结到根节点即可。

状压DP
一般以二进制状态作为状态划分的依据
用位运算表示状态,一个状态到另一个状态的表达式。
状压dp常见状态转移方程
假设现在的状态是state,所在位置v,要去u

f[v][state]=min{f[u][state|u]+dis[v][u]}

枚举state的子集
其实就是lowbit操作,找到子集中最后一个1.
把它减去再继续找,直到减没了为止。

for(int i=S;i;i=(i-1)&S)

有两种状态压缩dp的方式:
第一种是棋盘式的状态压缩dp,在棋盘里统计一些值,可能是方案数,可能是最大的数量等等。
这种状压dp会有各种形状的联通性

另一种是集合式的状态压缩dp问题,比如哪些点走过设置为1,没有走过设置为0。

Acwing 1064 小国王
每一行只和上一行的摆法有关系,和上上行的摆放方式无关。
f[i][k][state]==达到这个状态的方案数
i:摆放到第i行
k:摆放了k个棋子
state:第i行摆放方式 n位二进制数

什么样的状态能转移到f[i][k][state]?
1、要上一行合法,不能有两个相邻的国王。
2、要上一行和第i行不冲突
3、棋子的数量满足要求

a&b==0
a|b不能有两个相邻的1
f[i][k+cnt(a)][a]+=f[i-1][k][b]

这道题需要预处理状态a对应的所有状态b
然后在dp的时候从状态a对应的状态b中一个个枚举状态b

数位DP
计数DP
其实就是在数组里面存方案数。
leetcode 63 不同路径2
f[i][j]=f[i-1][j]+f[i][j-1]
初始条件f[0][0]=1代表能走到0,0就是一种方案。
ACwing 11 背包问题求方案数

#include<iostream>
using namespace std;
const int N=10010;
int cnt[N];
int f[N];
int mod=1000000007;
int main()
{
    int n,V;
    cin>>n>>V;
    for(int i=0;i<V;i++)
    {
        cnt[i]=1;
    }
    for(int i=0;i<n;i++)
    {
        int v,w;
        cin>>v>>w;
        for(int j=V;j>=v;j--)
        {
            int value=f[j-v]+w;
            if(value>f[j])
            {
                f[j]=value;
                cnt[j]=cnt[j-v];
            }
            else if(value==f[j])
            {
                cnt[j]=(cnt[j]+cnt[j-v])%mod;
            }
        }
    }
    cout<<cnt[V];
    return 0;
    
}

动态规划优化

DP与其他数据结构、技巧结合

矩阵递推(矩阵快速幂)
线段树+DP

用动态规划做预处理

**leetcode392 **

leetcode 42 接雨水
这道题要求预处理一个数左边和右边比他大的最大的数。

在这里插入图片描述

算法竞赛进阶指南习题

洛谷题目

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值