数字三角形模型

简单的线性 DP,通过枚举相邻位置进行转移

摘花生

从左上向右下转移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110;

int f[N][N];

int main()
{
int t;
cin >> t;
while(t -- )
{
memset(f, 0, sizeof(f));
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= m; j ++ )
{
int a;
cin >> a;
f[i][j] = max(f[i - 1][j], f[i][j - 1]) + a;
}
}
cout << f[n][m] << endl;
}
return 0;
}

最低通行费

由于必须在(2*n-1 步内走出去)所以其实本质上和摘花生一致,不能走回头路,必须从左上到右下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110;

int f[N][N], a[N][N];

int main()
{
memset(f, 0x3f, sizeof(f));
int n;
cin >> n;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
cin >> a[i][j];

for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
{
if(i == 1 && j == 1) f[1][1] = a[1][1];
else f[i][j] = min(f[i - 1][j], f[i][j - 1]) + a[i][j];
}
cout << f[n][n] << endl;
return 0;
}

方格取数

题意: 从左上走到右下,可以选择两条路,求出这两条路径上的点和最大

状态表示: , 总共走了 步,其中路径 1 在 方向上走了步,路径 2 在 方向上走了

由于上一题的推导可以得知 ,所以每一次转移时 , 方向上走的步数都可以确定

状态转移:

(当时只需一个)

从上转移

从左侧转移

从上转移,从左转移

从左转移, 从上转移

复杂度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 15;

int f[2 * N][N][N], a[N][N];

int main()
{
int n;
cin >> n;
int x, y, w;
while(cin >> x >> y >> w, x || y || w)
a[x][y] = w;

for (int k = 2; k <= 2 * n; k ++ ) // 从2开始,枚举横纵坐标的和
{
for (int i1 = 1; i1 <= n; i1 ++ )
{
for (int i2 = 1; i2 <= n; i2 ++ )
{
int j1 = k - i1, j2 = k - i2;
if(j1 <= n && j1 > 0 && j2 <= n && j2 > 0)
{
if(i1 == i2) w = a[i1][j1];
else w = a[i1][j1] + a[i2][j2];
f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1][i2] + w);
f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1 - 1][i2 - 1] + w);
f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1][i2 - 1] + w);
f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1 - 1][i2] + w);
}
}
}
}
cout << f[2 * n][n][n] << endl;
return 0;
}

传纸条

思路与方格取数类似,矩阵大小有一点区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 55;

int f[2 * N][N][N], w[N][N];

int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
cin >> w[i][j];

for (int k = 2; k <= n + m; 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 <= m && j2 > 0 && j2 <= m)
{
int t;
if(i1 == i2) t = w[i1][j1];
else t = w[i1][j1] + w[i2][j2];
f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1][i2] + t);
f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1 - 1][i2 - 1] + t);
f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1 - 1][i2] + t);
f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1][i2 - 1] + t);
}
}
}
}
cout << f[n + m][n][n] << endl;
return 0;
}

最长上升子序列模型

(LIS:Longest ascending subsequence)问题对应两种解法,一种是动态规划的 DP 方法,一种是二分的方法

怪盗基德的滑翔翼

从左向右求 LIS,从右向左求 LIS,取 max 即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 105;

int a[N], f1[N], f2[N];

int main()
{
int t;
cin >> t;
while(t -- )
{
int n;
cin >> n;
memset(a, 0, sizeof(a));
memset(f1, 0, sizeof(f1));
memset(f2, 0, sizeof(f2));
for (int i = 1; i <= n; i ++ )
cin >> a[i];
int ans = 0;
for (int i = 1; i <= n; i ++ )
{
f1[i] = 1;
for (int j = 1; j < i; j ++ )
{
if(a[i] < a[j]) f1[i] = max(f1[i], f1[j] + 1);
}
ans = max(ans, f1[i]);
}
for (int i = 1; i <= n; i ++ )
{
f2[i] = 1;
for (int j = 1; j < i; j ++ )
{
if(a[i] > a[j]) f2[i] = max(f2[i], f2[j] + 1);
}
ans = max(ans, f2[i]);
}
cout << ans << endl;
}
return 0;
}

登山

求单调上升在单调下降的最长子序列

左右分别跑一个 LIS 即可,注意最后的结果要减去这个点本身

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1005;

int a[N], f1[N], f2[N];

int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i ++ )
cin >> a[i];
for (int i = 1; i <= n; i ++ )
{
f1[i] = 1;
for (int j = 1; j < i; j ++ )
{
if(a[i] > a[j]) f1[i] = max(f1[i], f1[j] + 1);
}
}
for (int i = n; i >= 1; i -- )
{
f2[i] = 1;
for (int j = n; j > i; j -- )
{
if(a[i] > a[j]) f2[i] = max(f2[i], f2[j] + 1);
}
}

int ans = 0;
for (int i = 1; i <= n; i ++ )
{
ans = max(ans, f1[i] + f2[i] - 1); // 减去这个点本身
}
cout << ans << endl;

return 0;
}

合唱队形

求去掉 K 个人后满足先上升后下降,求最少去掉多少个人

向上一题跑一次即可,最后用总人数减去即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1005;

int a[N], f1[N], f2[N];

int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i ++ )
cin >> a[i];
for (int i = 1; i <= n; i ++ )
{
f1[i] = 1;
for (int j = 1; j < i; j ++ )
{
if(a[i] > a[j]) f1[i] = max(f1[i], f1[j] + 1);
}
}
for (int i = n; i >= 1; i -- )
{
f2[i] = 1;
for (int j = n; j > i; j -- )
{
if(a[i] > a[j]) f2[i] = max(f2[i], f2[j] + 1);
}
}

int ans = 0;
for (int i = 1; i <= n; i ++ )
{
ans = max(ans, f1[i] + f2[i] - 1);
}
cout << n - ans << endl;

return 0;
}

友好城市

将其中一侧进行进行排序,则最多能搭成的桥的个数就是另一侧的最长单调上升子序列

可以利用 pair 进行存储,方便进行排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
typedef pair<int, int> PII;

const int N = 5010;

PII bridge[N];

int f[N];

int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i ++ )
{
cin >> bridge[i].first >> bridge[i].second;
}
sort(bridge + 1, bridge + 1 + n);
int ans = 0;
for (int i = 1; i <= n; i ++ )
{
f[i] = 1;
for (int j = 1; j < i; j ++ )
{
if(bridge[i].second > bridge[j].second) f[i] = max(f[i], f[j] + 1);
}
ans = max(ans, f[i]);
}
cout << ans << endl;
return 0;
}

最大上升子序列和

本质上与最长上身子序列差不多,f 中存储的值变化了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;

int a[N], f[N];

int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i ++ )
{
cin >> a[i];
}
int ans = 0;
for (int i = 1; i <= n; i ++ )
{
f[i] = a[i];
for (int j = 1; j < i; j ++ )
{
if(a[i] > a[j]) f[i] = max(f[i], f[j] + a[i]);
}
ans = max(ans, f[i]);
}
cout << ans << endl;
return 0;
}

导弹拦截

没一套拦截的导弹只能只能比前一个值更小

问一套系统最多能拦截多少套,最少需要多少套系统才能拦截完所有导弹

第一问显然每套导弹拦截系统拦截导弹高度为最长不升子序列

第二问求导弹拦截系统的个数可以转化为求最长上升子序列长度

解法一:

dp 求 LIS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;

int n;
int a[N], f[N], g[N];

int main()
{
while(cin >> a[n]) n ++ ;

int ans1 = 0, ans2 = 0;
for (int i = 0; i < n; i ++ )
{
f[i] = 1;
g[i] = 1;
for (int j = 0; j < i; j ++ )
{
if(a[i] <= a[j]) f[i] = max(f[i], f[j] + 1);
else g[i] = max(g[i], g[j] + 1);
}
ans1 = max(ans1, f[i]);
ans2 = max(ans2, g[i]);
}
cout << ans1 << endl;
cout << ans2 << endl;
return 0;
}

解法二:

stl 进行二分求 LIS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;

int n;
int a[N];

int main()
{
while(cin >> a[n]) n ++ ;
vector<int> f, g;
for (int i = 0; i < n; i ++ )
{
int pos1 = upper_bound(f.begin(), f.end(), a[i], greater<int>()) - f.begin();
if(pos1 == f.size()) f.push_back(a[i]);
else f[pos1] = a[i];
int pos2 = lower_bound(g.begin(), g.end(), a[i]) - g.begin();
if(pos2 == g.size()) g.push_back(a[i]);
else g[pos2] = a[i];
}
cout << f.size() << endl;
cout << g.size() << endl;
return 0;
}

导弹防御系统

此系统拦截的导弹可以选择一直严格单调上升或者一直严格单调下降

可以利用搜素对每个导弹的状态进行枚举

搜索顺序分为两个阶段:

  • 从前往后枚举每颗导弹属于某个上升子序列,还是下降子序列;
  • 如果属于上升子序列,则枚举属于哪个上升子序列(包括新开一个上升子序列);
  • 如果属于下降子序列,也类似处理
迭代加深
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 60;

int n;
int h[N];
int up[N], down[N];

bool dfs(int depth, int u, int su, int sd)
{
// 如果上升序列个数 + 下降序列个数 > 总个数上限, 回溯
if(su + sd > depth) return false;
if(u == n) return true;

// 枚举放到上升子序列中的情况
bool flag = false;
for (int i = 1; i <= su; i ++ )
{
if(up[i] < h[u])
{
int t = up[i];
up[i] = h[u];
if(dfs(depth, u + 1, su, sd)) return true;
up[i] = t;
flag = true;
break;
// 新接上的末尾的数越小越好,所以直接break
}
}
if (!flag) // 如果不能放到任意一个序列的后面,则单独开一个新的序列
{
up[su + 1] = h[u];
if (dfs(depth, u + 1, su + 1, sd))
return true;
}
// 枚举放到下降子序列中的情况
flag = false;
for (int i = 1; i <= sd; i ++ )
{
if(down[i] > h[u])
{
int t = down[i];
down[i] = h[u];
if (dfs(depth, u + 1, su, sd)) return true;
down[i] = t;
flag = true;
break;
}
}
if (!flag) // 如果不能放到任意一个序列的后面,则单独开一个新的序列
{
down[sd + 1] = h[u];
if (dfs(depth, u + 1, su, sd + 1))
return true;
}
return false;
}

int main()
{
while(cin >> n, n)
{
for (int i = 0; i < n; i ++ ) cin >> h[i];

int depth = 0;

while (!dfs(depth, 0, 0, 0)) depth ++ ; // 迭代加深的搜索

cout << depth << endl;
}

return 0;
}
全局最小值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 55;

int a[N], ans, up[N], down[N], n;

void dfs(int u, int d, int t) // u表示上升的系统个数,d表示下降的系统个数,t表示第t个数
{
if(u + d >= ans) return;
if(t == n)
{
ans = min(ans, u + d);
// 更新全局最小值
return;
}
int i;
for (i = 1; i <= u; i ++ ) // 找到第一个末尾数小于a[t]的导弹系统
if(up[i] < a[t])
break;

int temp = up[i];
up[i] = a[t]; // 添加到该导弹系统中
dfs(max(u, i), d, t + 1);
up[i] = temp; // 恢复现场

for (i = 1; i <= d; i ++ ) // 找到第一个末尾数大于a[t]的导弹系统
if(down[i] > a[t])
break;

temp = down[i];
down[i] = a[t]; // 添加到该导弹系统中去
dfs(u, max(d, i), t + 1);
down[i] = temp; // 恢复现场
}

int main()
{
while(cin >> n, n)
{
ans = 2e9;
for (int i = 0; i < n; i ++ )
cin >> a[i];
dfs(0, 0, 0);
cout << ans << endl;
}
return 0;
}

最长公共上升子序列

前置 🧀(LCS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;

char a[N], b[N];
int f[N][N];
//f[i][j]代表由a的前i个字符,b的前j个字符组成的最长公共上升子序列的长度

int main()
{
int n, m;
cin >> n >> m;
cin >> a + 1 >> b + 1;
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= m; j ++ )
{
if(a[i] == b[j])
f[i][j] = f[i - 1][j - 1] + 1;
else
f[i][j] = max(f[i - 1][j], f[i][j - 1]);
}
}
cout << f[n][m] << endl;
return 0;
}

状态表示:

`var i=0,j=101; while(j--)i+=j;

  • f[i][j] 代表所有 a[1~i] 和 b[1~j] 中以 b[j] 结尾的公共上身子序列的集合
  • f[i][j] 的值等于该集合中子序列中长度的最大值

状态计算(集合的划分):

  • 不包括 a[i]的子集,最大值是 f[i-1][j];
  • 包括 a[i] 的子集,将这个子集继续划分,依据是子序列的倒数第二个元素在 b[] 中是哪个数
    • 子序列只包含 b[j]一个数,长度是 1
    • 子序列的倒数第二个数是 b[i]的集合,最大长度是 f[i - 1][1] + 1;
    • ...
    • 子序列的倒数第二个数是 b[j - 1]的集合,最大长度是 f[i - 1][j - 1] + 1;

按照上述思路实现,需要三重循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= n; j ++ )
{
f[i][j] = f[i - 1][j];
if(a[i] == b[j]) // 满足公共
{
int maxv = 1;
for (int k = 1; k < j; k ++ ) // 满足上升
if(a[i] > b[k])
maxv = max(maxv, f[i - 1][k] + 1);
f[i][j] = max(f[i][j], maxv); // 转移
}
}
}

注意到每次循环求得的 maxv 是满足 的前缀最大值

因此可以直接将 maxv 提到第一层循环外面,减少重复计算,此时只剩下两重循环

最终答案枚举子序列结尾取最大值即可,此时复杂度由 降低到了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 3010;

int a[N], b[N];

int f[N][N];

int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1; i <= n; i ++ ) cin >> b[i];

for (int i = 1; i <= n; i ++ )
{
int maxv = 1;
for (int j = 1; j <= n; j ++ )
{
f[i][j] = f[i - 1][j];
if(a[i] == b[j]) f[i][j] = max(f[i][j], maxv);
if(a[i] > b[j]) maxv = max(maxv, f[i - 1][j] + 1);
}
}
int res = 0;
for (int i = 1; i <= n; i ++ )
res = max(res, f[n][i]);
cout << res << endl;
return 0;
}

状态机模型

大盗阿福

题意:共 个数,选择任意不相邻的数,使得总和最大

解析: 表示前 个店铺的最大收益

不抢第 个店铺时的最大收益为

由于不能抢相邻的,所以抢第 i 个店铺时一定不能抢第 个店铺

抢第 个店铺时的最大收益位

时,

状态表示: 表示选择第 个店铺, 表示不选择第 个店铺

状态转移:

复杂度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1e5 + 10, INF = 0x3f3f3f3f;

int f[N][2], w[N];

// f(i, 1) 选最后一个店铺
// f(i, 0) 不选择最后一个店铺

int main()
{
int t;
cin >> t;
while(t -- )
{
int n;
cin >> n;
f[0][0] = 0, f[0][1] = -INF;
for (int i = 1; i <= n; i ++ )
{
int w;
cin >> w;
f[i][0] = max(f[i - 1][0], f[i - 1][1]);
f[i][1] = f[i - 1][0] + w;
}
cout << max(f[n][0], f[n][1]) << endl;
}
return 0;
}

股票买卖 Ⅳ

题意:给定一个长度为 的数组,数组中的第 个数字表示一个给定股票在第 天的价格。

求能获取的最大利润,最多可以完成 笔交易

状态表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 5, K = 105;

int f[N][K][2], w[N];

// f[i][j][k] 代表第i天, 交易j次, 是否买入

int main()
{
int n, k;
cin >> n >> k;
memset(f, -0x3f, sizeof f);

for (int i = 1; i <= n; i ++ ) cin >> w[i];

for (int i = 0; i <= n; i ++ ) f[i][0][0] = 0;

for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= k; j ++ )
{
f[i][j][0] = max(f[i - 1][j][0], f[i - 1][j][1] + w[i]); // 选择是否出售
f[i][j][1] = max(f[i - 1][j][1], f[i - 1][j - 1][0] - w[i]); // 选择是否买入(交易次数加一)
}
}

int ans = 0;
// 枚举交易次数,找到其中利润最大的一次
for (int i = 1; i <= k; i ++ ) ans = max(ans, f[n][i][0]);

cout << ans << endl;
return 0;
}

滚动数组优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 5, K = 105;

int f[2][K][2], w[N];

// f[i][j][k] 代表第i天, 交易j次, 是否买入

int main()
{
int n, k;
cin >> n >> k;

for (int i = 1; i <= n; i ++ ) cin >> w[i];

memset(f, -0x3f, sizeof f);
f[0][0][0] = 0;

for (int i = 1; i <= n; i ++ )
{
for (int j = 0; j <= k; j ++ )
{
f[i & 1][j][0] = f[(i - 1) & 1][j][0]; // 无论j是否大于0,
if (j) f[i & 1][j][0] = max(f[i & 1][j][0], f[(i - 1) & 1][j - 1][1] + w[i]); // j大于0才能买
f[i & 1][j][1] = max(f[(i - 1) & 1][j][1], f[(i - 1) & 1][j][0] - w[i]);
}
}

int ans = 0;

for (int j = 0; j <= k; j ++ ) ans = max(ans, f[n & 1][j][0]);

cout << ans << endl;
return 0;
}

股票买卖 Ⅴ

在上一题的基础上加上两个限制,去除交易次数的限制

  • 不能同时参与多笔交易(必须在再次购买前出售掉之前的股票)
  • 卖出股票后,无法在第二天买入股票 (即冷冻期为 1 天)

考虑将当前这一天的状态分为以下三种情况

  1. 空仓
  2. 冷冻期(刚刚卖了股票)
  3. 持仓

状态表示: 表示前 天,第 天的状态是 的方案

状态转移:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 5;

int w[N], f[N][3];

// j=0空仓
// j=1持仓
// j=2冷冻期

int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i ++ )
cin >> w[i];

memset(f, -0x3f, sizeof f);

f[0][0] = 0;

for (int i = 1; i <= n; i ++ )
{
f[i][0] = max(f[i - 1][0], f[i - 1][2]);
f[i][1] = max(f[i - 1][1], f[i - 1][0] - w[i]); // 买入
f[i][2] = f[i - 1][1] + w[i]; // 卖出
}
cout << max(f[n][0], f[n][2]) << endl;
return 0;
}

滚动数组优化

&1 即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 5;

int w[N], f[2][3];

// j=0空仓
// j=1持仓
// j=2冷冻期

int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i ++ )
cin >> w[i];

memset(f, -0x3f, sizeof f);

f[0][0] = 0;

for (int i = 1; i <= n; i ++ )
{
f[i & 1][0] = max(f[(i - 1) & 1][0], f[(i - 1) & 1][2]);
f[i & 1][1] = max(f[(i - 1) & 1][1], f[(i - 1) & 1][0] - w[i]); // 买入
f[i & 1][2] = f[(i - 1) & 1][1] + w[i]; // 卖出
}
cout << max(f[n & 1][0], f[n & 1][2]) << endl;
return 0;
}

设计密码

给定一个字符串 ,和一个整数 现需设计一个密码 , 需满足:

的长度是 只包含小写英文字母 不包含子串 求该密码的 方案数,答案对 取模

做法:从前往后进行线性构造

如果当前设计的密码是合法的话,在它后面添上一个新字符,可能会导致新密码不合法,就是新密码的后缀子串中出现了 , 利用参数 来表示母串后缀与模式串匹配的最大长度

状态表示: 表示长度为 且后缀与模式串匹配的最大长度为 的方案

状态转移:

时间复杂度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 55, mod = 1e9 + 7;

int n, m;
char s[N];
int f[N][N], ne[N];

int main()
{
cin >> n >> s + 1;
m = strlen(s + 1);
// KMP预处理前缀表
for (int i = 2, j = 0; i <= m; i ++ )
{
while (j && s[i] != s[j + 1]) j = ne[j];
if (s[i] == s[j + 1]) j ++ ;
ne[i] = j;
}
// DP
f[0][0] = 1;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
for (char ch = 'a'; ch <= 'z'; ch ++ ) // 枚举第i+1个字符
{
int ptr = j; // 计算枚举到第i+1个字符后,后缀匹配的最大长度
while (ptr && s[ptr + 1] != ch) ptr = ne[ptr];
if (s[ptr + 1] == ch) ptr ++ ;
f[i + 1][ptr] = (f[i + 1][ptr] + f[i][j]) % mod; // 更新i+1的状态
}
// 统计所有目标状态
int res = 0;
for (int j = 0; j < m; j ++ ) res = (res + f[n][j]) % mod;
cout << res << endl;
return 0;
}