Dp专题
文章目录
状态机模型
简介:
比如说当一维度表达不清楚的时候,你们我们会通常会开第二维( f [ i ] [ 2 ] f[i][2] f[i][2])来表示清楚状态。
例题
例1:AcWing 1049. 大盗阿福
Problem:
平面上有
n
n
n个点,每个点有一个权值
a
i
a_i
ai,要求在平面中取若干个点,取得最大值(不呢取连续两个点)
Solution:
f
[
i
]
[
0
]
f[i][0]
f[i][0]取到第
i
i
i个点,且第
i
i
i个点不取的情况
f
[
i
]
[
1
]
f[i][1]
f[i][1]取到第
i
i
i个点,且第
i
i
i个点取的情况
显然
f
[
i
]
[
0
]
=
m
a
x
(
f
[
i
−
1
]
[
1
]
,
f
[
i
−
1
]
[
0
]
)
f[i][0]=max(f[i-1][1],f[i-1][0])
f[i][0]=max(f[i−1][1],f[i−1][0])
f
[
i
]
[
1
]
=
f
[
i
−
1
]
[
0
]
+
a
[
i
]
f[i][1]=f[i-1][0]+a[i]
f[i][1]=f[i−1][0]+a[i]
例2:AcWing 1057. 股票买卖 IV
Problem:
给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润,你最多可以完成 k 笔交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。一次买入卖出合为一笔交易。
Solution:
f
[
0
]
[
i
]
[
j
]
f[0][i][j]
f[0][i][j]取到第
i
i
i个点,交易
j
j
j次且第
i
i
i个股票没有的情况
f
[
1
]
[
i
]
[
j
]
f[1][i][j]
f[1][i][j]取到第
i
i
i个点,交易
j
j
j次且第
i
i
i个股票有的情况
显然每个点都从上面一个推导过来,所以可以把
i
i
i给消除
f
[
0
]
[
j
]
=
m
a
x
(
f
[
0
]
[
j
]
,
f
[
1
]
[
j
]
+
x
)
;
f
[
1
]
[
j
]
=
m
a
x
(
f
[
1
]
[
j
]
,
f
[
0
]
[
j
−
1
]
−
x
)
;
f[0][j]=max(f[0][j],f[1][j]+x); f[1][j]=max(f[1][j],f[0][j-1]-x);
f[0][j]=max(f[0][j],f[1][j]+x);f[1][j]=max(f[1][j],f[0][j−1]−x);
例2:AcWing 1057. 股票买卖 V
Problem:
给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
Solution:
f[i][0]表示第i天手里没有票(刚卖出去)对应图中的“没有”
f[i][1]表示第i天手里有票(刚买进来) 对应图中的“有”
f[i][2]表示第i天是冷冻期之后(手里没有票,但是可以买入票)对应图中的“冷冻期”
状态压缩DP
简介:
对一些状态进行压缩(一般为二进制压缩)从而可以更好的表示状态以及转移
常用模板:
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前i个,在j这个状态时的方案数/大小
e
d
g
e
[
i
]
[
j
]
表
示
这
两
个
状
态
是
不
是
可
以
转
移
,
C
a
n
[
i
]
表
示
这
个
状
态
本
身
是
不
是
合
法
edge[i][j]表示这两个状态是不是可以转移,Can[i]表示这个状态本身是不是合法
edge[i][j]表示这两个状态是不是可以转移,Can[i]表示这个状态本身是不是合法
- 预处理出合理的方案
- 处理合法方案之间的关系
- f [ i ] [ j ] = f [ i − 1 ] [ s ] ( e d g e [ j ] [ s ] ) f[i][j]=f[i-1][s](edge[j][s]) f[i][j]=f[i−1][s](edge[j][s])
例题
例1: [SCOI2005]互不侵犯
Problem:
[SCOI2005]互不侵犯
Solution:
显然我们可以先求出每行可以放置国王的合法状态,然后又可以发现可退出行与行之间状态之间的转移,显然当两行a&b==0 && check(A|B)是才合法。
这样统计出来所以的状态之后我们就可以开始状态压缩DP了,
f
[
i
]
[
j
]
[
k
]
f[i][j][k]
f[i][j][k]表示在前i行放置j个国王,这行的状态是j时候合法的方案数
f
[
i
]
[
j
]
[
a
]
+
=
f
[
i
−
1
]
[
j
−
c
]
[
b
]
;
f[i][j][a] += f[i - 1][j - c][b];
f[i][j][a]+=f[i−1][j−c][b];
例2:[NOI2001] 炮兵阵地
Problem: [NOI2001] 炮兵阵地
Solution:
因为影响的范围是两行,所以我们的状态应该设置为
f
[
i
]
[
j
]
[
k
]
f[i][j][k]
f[i][j][k]表示在前i行,这一行是j,前一行是k时候最多能放置的炮兵数量,这样我们就可以像上面一样状态压缩dp了
区间DP
简介:
对一些区间进行操作,并且有很多的情况是相邻之间的操作,而且总输出让我们求的是方案数和最大最小值
常用模板:
f [ i ] [ j ] f[i][j] f[i][j]表示合并到(i-j)的时候的最小最大值,以及方案数
for(int len=2;len<=n;len++){
for(int l=1;l+len-1<=n;l++){
int r=l+len-1;
for(int k=l;k<=r;l++)
f[i][j]= f[i][k] + f[k][j]
}
}
例题
例1:石子合并
Problem: 石子合并
Solution:
显然我们可以先把环形复制为第二列,这样之后我们就可以之间对整个序列进行操作,通过上面的模板就可以发现只需要知道合并时候一段之间的代价就可以直接用模板解决。
很容易就可以想出来用前缀和处理
m
i
d
p
[
l
]
[
r
]
=
m
i
n
(
m
i
d
p
[
l
]
[
r
]
,
m
i
d
p
[
l
]
[
k
]
+
m
i
d
p
[
k
+
1
]
[
r
]
+
s
u
m
[
r
]
−
s
u
m
[
l
−
1
]
)
;
midp[l][r]=min(midp[l][r],midp[l][k]+midp[k+1][r]+sum[r]-sum[l-1]);
midp[l][r]=min(midp[l][r],midp[l][k]+midp[k+1][r]+sum[r]−sum[l−1]);
m
a
d
p
[
l
]
[
r
]
=
m
a
x
(
m
a
d
p
[
l
]
[
r
]
,
m
a
d
p
[
l
]
[
k
]
+
m
a
d
p
[
k
+
1
]
[
r
]
+
s
u
m
[
r
]
−
s
u
m
[
l
−
1
]
)
;
madp[l][r]=max(madp[l][r],madp[l][k]+madp[k+1][r]+sum[r]-sum[l-1]);
madp[l][r]=max(madp[l][r],madp[l][k]+madp[k+1][r]+sum[r]−sum[l−1]);
例2:Zuma
Problem: Zuma
Solution:
先求出来两个之间的关系,然后就套用模板
例3:能量项链
Problem: Acwing 30
Solution: f[l][k]+f[k+1][r]+a[l]*a[k+1]*a[r+1]
树形DP
简介: 树上进行操作的一些dp题目
常用模板:
f[i][0/1] 表示以i为根的子树…
I void dfs(int x,int fa){
for(RI i=head[x];i;i=edge[i].next){
int to=edge[i].to;if(to==fa)continue;
dep[to]=dep[x]+edge[i].val;dfs(to,x);
}
}
例题
例1:树的最长路径
Problem: 树的直径
Solution:
两次dfs即可
例2:数字转换
**Problem:**数字转换
Solution:
可以发现每个数的所有因数的和只有一种,所以考虑将自己和自己的因数之和连边,这样就变成了一棵树了,根据题目意思要求最多的变换次数,就是让我们求一手树 的直径
例3:树的中心
Problem: 给定一棵树,树中包含 n 个结点(编号1~n)和 n−1 条无向边,每条边都有一个权值。
请你在树中找到一个点,使得该点到树中其他结点的最远距离最近。
Solution: 我们考虑每一个点,远离它的Max distance 必定是它往上走,或者是它往下面走,然后进行分类讨论,算出来向上和向下两种最值就可以了
数位DP
简介:
专门对于一些要求的数字很大,但是可以拆开每一位数考虑
常用模板:
1.求[a,b]直接满足的方案,就是求1~b的方案和1-a-1的方案数相减
2.把每一位数拆开来,f[i][j]表示最高位数是的时候,有j位的方案数
例题
例1:Windy数
Problem: Windy 定义了一种 Windy 数:不含前导零且相邻两个数字之差至少为 2 的正整数被称为 Windy 数。
Solution:
for (int i = nums.size() - 1; i >= 0; i -- )
{
int x = nums[i];
for (int j = i == nums.size() - 1; j < x; j ++ )
if (abs(j - last) >= 2)
res += f[i + 1][j];
if (abs(x - last) >= 2) last = x;
else break;
if (!i) res ++ ;
}
// 特殊处理有前导零的数
for (int i = 1; i < nums.size(); i ++ )
for (int j = 1; j <= 9; j ++ )
res += f[i][j];
例2:度的数量
**Problem:**求给定区间 [X,Y] 中满足下列条件的整数个数:这个数恰好等于 K 个互不相等的 B 的整数次幂之和。
Solution: 把这个数进行k进制的拆分之后可以发现,就可以每一位考虑要不要填1,
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
+
f
[
i
−
1
]
[
j
−
1
]
;
f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
f[i][j]=f[i−1][j]+f[i−1][j−1]; ,如果要填那么
r
e
s
+
=
f
[
i
]
[
K
−
l
a
s
t
−
1
]
;
res += f[i][K - last - 1];
res+=f[i][K−last−1];,最后判断一下,这个数本身是不是合法的