数字三角形模型
简单的线性 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 ++ ) { 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 ; } } 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) { if (u + d >= ans) return ; if (t == n) { ans = min (ans, u + d); return ; } int i; for (i = 1 ; i <= u; i ++ ) 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 ++ ) 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];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];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];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];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 ]; if (j) f[i & 1 ][j][0 ] = max (f[i & 1 ][j][0 ], f[(i - 1 ) & 1 ][j - 1 ][1 ] + w[i]); 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 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 ];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 ];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 ); 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; } 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 ++ ) { int ptr = j; 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; } int res = 0 ; for (int j = 0 ; j < m; j ++ ) res = (res + f[n][j]) % mod; cout << res << endl; return 0 ; }