本博客根据AcwingDP问题总结:只做资料的搬运工
1、 数字三角形模型
Acwing1027. 方格取数
设有
N
×
N
N \times N
N×N 的方格图,我们在其中的某些方格中填入正整数,而其它的方格中则放入数字0。如下图所示:
某人从图中的左上角 A A A 出发,可以向下行走,也可以向右行走,直到到达右下角的 B B B 点。
在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
此人从 A A A 点到 B B B 点共走了两次,试找出两条这样的路径,使得取得的数字和为最大。
输入格式
第一行为一个整数
N
N
N,表示
N
×
N
N \times N
N×N 的方格图。
接下来的每行有三个整数,第一个为行号数,第二个为列号数,第三个为在该行、该列上所放的数。
行和列编号从 1 1 1 开始。
一行" 000 0 0 0 000"表示结束。
输出格式
输出一个整数,表示两条路径上取得的最大的和。
数据范围
N
≤
10
N\leq10
N≤10
输入样例:
8
2 3 13
2 6 6
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0
输出样例:
67
#include<bits/stdc++.h>
using namespace std;
const int N = 15;
int f[2*N][N][N];
int q[N][N];
int n;
int main(){
scanf("%d",&n);
int x,y,k;
while(scanf("%d %d %d", &x, &y, &k),x) q[x][y] = k;
for(int k = 2;k<=2*n;k++){
for(int i1 = 1;i1<=n;i1++){
for(int i2 =1;i2<=n;i2++){
int j1 = k-i1,j2 = k-i2;
if(j1>0&&j1<=n&&j2>0&&j2<=n){
int t = q[i1][j1];
if(i1!=i2) t+=q[i2][j2];
int &x = f[k][i1][i2];
x = max(f[k-1][i1-1][i2-1]+t,x);
x = max(f[k-1][i1][i2]+t,x);
x = max(f[k-1][i1][i2-1]+t,x);
x = max(f[k-1][i1-1][i2]+t,x);
}
}
}
}
cout<<f[2*n][n][n];
return 0;
}
2、 序列DP模型
Acwing1017. 怪盗基德的滑翔翼
怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。
而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。
有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。
不得已,怪盗基德只能操作受损的滑翔翼逃脱。
假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。
初始时,怪盗基德可以在任何一幢建筑的顶端。
他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。
因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。
他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。
请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?
输入格式
输入数据第一行是一个整数
K
K
K,代表有
K
K
K组测试数据。
每组测试数据包含两行:第一行是一个整数 N N N,代表有 N N N幢建筑。第二行包含 N N N个不同的整数,每一个对应一幢建筑的高度 h h h,按照建筑的排列顺序给出。
输出格式
对于每一组测试数据,输出一行,包含一个整数,代表怪盗基德最多可以经过的建筑数量。
数据范围
1
≤
K
≤
100
1\leq K\leq100
1≤K≤100,
1
≤
N
≤
100
1\leq N\leq100
1≤N≤100,
0
<
h
<
10000
0 <h<10000
0<h<10000
输入样例:
3
8
300 207 155 299 298 170 158 65
8
65 158 170 298 299 155 207 300
10
2 1 3 4 5 6 7 8 9 10
输出样例:
6
6
9
解题思路:一次最长上升子序列,一次最长下降子序列
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int k,n,h;
int q[N],f[N];
int main(){
scanf("%d",&k);
while(k--){
int res = 0;
scanf("%d",&n);
for(int i =0;i<n;i++) scanf("%d",&q[i]);
for(int i = 0;i<n;i++){
f[i] =1;
for(int j = 0;j<i;j++){
if(q[i]>q[j]){
f[i] = max(f[i],f[j]+1);
}
}
res = max(res,f[i]);
}
for(int i = n-1;i>=0;i--){
f[i] = 1;
for(int j=n-1;j>i;j--){
if(q[j]<q[i]){
f[i] = max(f[i],f[j]+1);
}
}
res = max(res,f[i]);
}
printf("%d\n",res);
}
return 0;
}
Acwing1010. 拦截导弹
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。
但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
某天,雷达捕捉到敌国的导弹来袭。
由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于 30000 30000 30000的正整数,导弹数不超过 1000 1000 1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
共一行,输入导弹依次飞来的高度。
输出格式
第一行包含一个整数,表示最多能拦截的导弹数。
第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。
数据范围
雷达给出的高度数据是不大于
30000
30000
30000 的正整数,导弹数不超过
1000
1000
1000。
输入样例:
389 207 155 300 299 170 158 65
输出样例:
6
2
解法:一次最长上升子序列,一次贪心算法
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
int f[N],q[N],s[N];
int main(){
int x,n =0;
while(~scanf("%d",&x)){
q[++n] = x;
}
int res = 0;
int cnt = 0;
for(int i =1 ;i <=n ;i++){
f[i] = 1;
for(int j =1 ;j<i ;j++){
if(q[i]<=q[j]){
f[i] =max(f[i],f[j]+1);
}
}
res = max(res,f[i]);
int k = 0 ;
while(k<cnt && s[k] < q[i]) k++;
if(k==cnt) s[cnt++] = q[i];
else s[k] = q[i];
}
printf("%d\n",res);
printf("%d\n",cnt);
}
Leetcode1473.粉刷房子III
在一个小城市里,有
m
m
m 个房子排成一排,你需要给每个房子涂上
n
n
n 种颜色之一(颜色编号为
1
1
1 到
n
n
n )。有的房子去年夏天已经涂过颜色了,所以这些房子不可以被重新涂色。
我们将连续相同颜色尽可能多的房子称为一个街区。(比方说 houses = [1,2,2,3,3,2,1,1]
,它包含 5 个街区 [{1}, {2,2}, {3,3}, {2}, {1,1}]
。)
给你一个数组 h o u s e s houses houses ,一个 m ∗ n m * n m∗n 的矩阵 cost 和一个整数 target ,其中:
houses[i]
:是第
i
i
i 个房子的颜色,
0
0
0 表示这个房子还没有被涂色。
cost[i][j]
:是将第
i
i
i 个房子涂成颜色
j
+
1
j+1
j+1 的花费。
请你返回房子涂色方案的最小总花费,使得每个房子都被涂色后,恰好组成 target 个街区。如果没有可用的涂色方案,请返回
−
1
-1
−1 。
示例 1:
输入:houses = [0,0,0,0,0], cost = [[1,10],[10,1],[10,1],[1,10],[5,1]], m = 5, n = 2, target = 3
输出:9
解释:房子涂色方案为 [1,2,2,1,1]
此方案包含 target = 3 个街区,分别是 [{1}, {2,2}, {1,1}]。
涂色的总花费为 (1 + 1 + 1 + 1 + 5) = 9。
示例 2:
输入:houses = [0,2,1,2,0], cost = [[1,10],[10,1],[10,1],[1,10],[5,1]], m = 5, n = 2, target = 3
输出:11
解释:有的房子已经被涂色了,在此基础上涂色方案为 [2,2,1,2,2]
此方案包含 target = 3 个街区,分别是 [{2,2}, {1}, {2,2}]。
给第一个和最后一个房子涂色的花费为 (10 + 1) = 11。
示例 3:
输入:houses = [0,0,0,0,0], cost = [[1,10],[10,1],[1,10],[10,1],[1,10]], m = 5, n = 2, target = 5
输出:5
示例 4:
输入:houses = [3,1,2,3], cost = [[1,1,1],[1,1,1],[1,1,1],[1,1,1]], m = 4, n = 3, target = 3
输出:-1
解释:房子已经被涂色并组成了 4 个街区,分别是 [{3},{1},{2},{3}] ,无法形成 target = 3 个街区。
提示:
m
=
=
h
o
u
s
e
s
.
l
e
n
g
t
h
=
=
c
o
s
t
.
l
e
n
g
t
h
m == houses.length == cost.length
m==houses.length==cost.length
n
=
=
c
o
s
t
[
i
]
.
l
e
n
g
t
h
n == cost[i].length
n==cost[i].length
1
≤
m
≤
100
1 \leq m \leq 100
1≤m≤100
1
≤
n
≤
20
1 \leq n \leq 20
1≤n≤20
1
≤
t
a
r
g
e
t
≤
m
1 \leq target \leq m
1≤target≤m
0
≤
h
o
u
s
e
s
[
i
]
≤
n
0\leq houses[i] \leq n
0≤houses[i]≤n
1
≤
c
o
s
t
[
i
]
[
j
]
≤
1
0
4
1 \leq cost[i][j] \leq 10^4
1≤cost[i][j]≤104
解决代码
class Solution {
public:
int minCost(vector<int>& houses, vector<vector<int>>& cost, int m, int n, int target) {
const int INF = 0x3f3f3f3f;
int f[105][25][105];
// 无效的状态
for(int i = 0 ;i<=m ; i++){
for(int j = 0 ; j<=n ;j++){
f[i][j][0] = INF;
}
}
for(int i = 1 ;i <=m ; i++){
int color = houses[i-1];
for(int j = 1 ;j<=n ;j++){
for(int k = 1;k<=target;k++){
//没有分区数大于房子数
if(k > i){
f[i][j][k] = INF;
continue;
}
if(color!=0){
// 涂了某种颜色
if(j==color){
int cur = INF;
for(int p = 1 ;p<=n ; p++){
if(p!=j){
cur = min(cur , f[i-1][p][k-1]);
}
}
f[i][j][k] = min(cur,f[i-1][j][k]);
}else{
f[i][j][k] =INF;// 无效状态
}
}else{
int u = cost[i-1][j-1]; //第i个房间第j个颜色的花费
int cur =INF;
for(int p = 1 ;p<=n;p ++){
if(p!=j ) cur = min(cur ,f[i-1][p][k-1]+u);
}
f[i][j][k] = min(cur,f[i-1][j][k]+u);
}
}
}
}
int ans = INF;
for(int i = 1 ; i<=n ;i ++){
ans = min(ans,f[m][i][target]);
}
return ans == INF? -1:ans;
}
};
3、 区间DP模型
Acwing282. 石子合并
设有 N 堆石子排成一排,其编号为
1
,
2
,
3
,
…
,
N
1,2,3,…,N
1,2,3,…,N。
每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N N N 堆石子合并成为一堆。
每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。
例如有
4
4
4 堆石子分别为 1 3 5 2
, 我们可以先合并
1
、
2
1、2
1、2 堆,代价为
4
4
4,得到 4 5 2
, 又合并
1
,
2
1,2
1,2 堆,代价为
9
9
9,得到 9 2
,再合并得到
11
11
11,总代价为
4
+
9
+
11
=
24
4+9+11=24
4+9+11=24;
如果第二步是先合并
2
,
3
2,3
2,3 堆,则代价为
7
7
7,得到 4 7
,最后一次合并代价为
11
11
11,总代价为
4
+
7
+
11
=
22
4+7+11=22
4+7+11=22。
问题是:找出一种合理的方法,使总的代价最小,输出最小代价。
输入格式
第一行一个数
N
N
N 表示石子的堆数
N
N
N。
第二行 N N N个数,表示每堆石子的质量(均不超过 1000 1000 1000)。
输出格式
输出一个整数,表示最小代价。
数据范围
1
≤
N
≤
300
1 \leq N \leq300
1≤N≤300
输入样例:
4
1 3 5 2
输出样例:
22
#include <bits/stdc++.h>
using namespace std;
const int N = 310;
int n;
int s[N];
int dp[N][N];
int f[N][N];
int main()
{
scanf("%d",&n);
memset( dp,0x3f ,sizeof dp);
for(int i = 1 ;i <= n ;i ++ ) {scanf("%d",&s[i]),s[i] += s[i-1];f[i][i] = i,dp[i][i] = 0;} ;
for(int len = 2;len <= n ;len ++ ){
for(int l = 1 ; l + len -1 <= n ;l ++){
int r = l + len - 1 ;//dp[i][j]
for(int k = f[l][r-1] ; k<= f[l+1][r] ; k++){
if(dp[l][k] + dp[k+1][r] + s[r] - s[l-1] < dp[l][r]){
dp[l][r] = dp[l][k] + dp[k+1][r] + s[r] - s[l-1];
f[l][r] = k;
}
}
}
}
printf("%d",dp[1][n]);
return 0;
}