参考《算法竞赛进阶指南》 、AcWing题库
题目描述
阿福是一名经验丰富的大盗。趁着月黑风高,阿福打算今晚洗劫一条街上的店铺。
这条街上一共有 N N N 家店铺,每家店中都有一些现金。
阿福事先调查得知,只有当他同时洗劫了两家相邻的店铺时,街上的报警系统才会启动,然后警察就会蜂拥而至。
作为一向谨慎作案的大盗,阿福不愿意冒着被警察追捕的风险行窃。
他想知道,在不惊动警察的情况下,他今晚最多可以得到多少现金?
输入格式
输入的第一行是一个整数 T T T ,表示一共有 T T T 组数据。
接下来的每组数据,第一行是一个整数 N N N ,表示一共有 N N N 家店铺。
第二行是 N N N 个被空格分开的正整数,表示每一家店铺中的现金数量。
每家店铺中的现金数量均不超过1000。
输出格式
对于每组数据,输出一行。
该行包含一个整数,表示阿福在不惊动警察的情况下可以得到的现金数量。
数据范围
1 ≤ T ≤ 50 1 \le T \le 50 1 ≤ T ≤ 50 ,
1 ≤ N ≤ 1 0 5 1 \le N \le 10^5 1 ≤ N ≤ 1 0 5
输入样例 :
输出样例 :
样例解释
对于第一组样例,阿福选择第2家店铺行窃,获得的现金数量为8。
对于第二组样例,阿福选择第1和4家店铺行窃,获得的现金数量为10+14=24。
算法分析
在DP的阶段表示中,如果前一个阶段的决策会影响到后面的阶段的决策,那么我们就可以用状态机的形式来做,具体做法就是描述清楚当前的每一个决策
理解题意
限制
目标
相比于01 01 01 背包,限制条件从物品的体积变为一个条件限制: 不能连续选择物品,也就是我们此时需要关注物品 i i i 能否被选。原本限制体积的状态在本题中用于表示是否选择。可以用状态机模型清晰的表示不同状态间的关系.
状态机模型
状态机: 用一些确定的状态及状态间的关系描述一个过程.
本题中需要表示第i i i 个物品选/不选两种状态, 且不能连续选择. 状态间的关系如图:
上图具体含义:
依照状态机模型我们就很容易做D P DP D P 分析了.
D P DP D P 分析
状态表示 d p ( i , 0 ) dp(i, 0) d p ( i , 0 ) /d p ( i , 1 ) dp(i, 1) d p ( i , 1 )
集合: d p ( i , j ) dp(i, j) d p ( i , j ) 表示所有走了i i i 步且最终状态为j j j 的路径. j ∈ [ 0 , 1 ] j\in [0, 1] j ∈ [ 0 , 1 ] .
属性: Max
状态计算
依据最后一步的选择, 在状态机模型中对应当前状态从何而来:
d p ( i , 0 ) dp(i, 0) d p ( i , 0 ) : 其上一个状态可以是d p ( i − 1 , 0 ) dp(i - 1, 0) d p ( i − 1 , 0 ) /
d p ( i − 1 , 1 ) dp(i - 1, 1) d p ( i − 1 , 1 ) , 且对应边的权重均为0
,有d p ( i , 0 ) = m a x { d p ( i − 1 , 0 ) , d p ( i − 1 , 1 ) } dp(i, 0) = max\lbrace dp(i - 1, 0), dp(i - 1, 1)\rbrace d p ( i , 0 ) = ma x { d p ( i − 1 , 0 ) , d p ( i − 1 , 1 )} .
d p ( i , 1 ) dp(i, 1) d p ( i , 1 ) : 其上一个状态只能是d p ( i − 1 , 0 ) dp(i - 1, 0) d p ( i − 1 , 0 ) . 且对应边的权重为w i w_i w i , 即物品i i i 的价值,有p ( i , 1 ) = d p ( i − 1 , 0 ) + w i p(i, 1) = dp(i - 1, 0) + w_i p ( i , 1 ) = d p ( i − 1 , 0 ) + w i .
因为阿福的每个决策都与图中长度为N N N 的路径一一对应, 所以D P DP D P 的计算过程相当于将所有路径的可能都计算了一遍.
Solution
状态机动态规划
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> using namespace std;const int N = 1e5 + 10 , INF = 0x3f3f3f3f ;int money[N], f[N][2 ];int main () { int T; cin >> T; while (T--) { int n; cin >> n; for (int i = 1 ; i <= n; ++i) cin >> money[i]; f[0 ][0 ] = 0 , f[0 ][1 ] = -INF; for (int i = 1 ; i <= n; ++i) { f[i][0 ] = max (f[i - 1 ][0 ], f[i - 1 ][1 ]); f[i][1 ] = f[i - 1 ][0 ] + money[i]; } cout << max (f[n][0 ], f[n][1 ]) << endl; } }
线性动态规划
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> using namespace std;const int N = 1e5 + 10 ;int money[N], f[N];int main () { int T; cin >> T; while (T--) { int n; cin >> n; for (int i = 1 ; i <= n; ++i) cin >> money[i]; f[0 ] = 0 , f[1 ] = money[1 ]; for (int i = 2 ; i <= n; ++i) { f[i] = max (f[i - 1 ], f[i - 2 ] + money[i]); } cout << f[n] << endl; } }
题目描述
给定一个长度为 N N N 的数组,数组中的第 i i i 个数字表示一个给定股票在第 i i i 天的价格。
设计一个算法来计算你所能获取的最大利润,你最多可以完成 k k k 笔交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。一次买入卖出合为一笔交易。
输入格式
第一行包含整数 N N N 和 k k k ,表示数组的长度以及你可以完成的最大交易数量。
第二行包含 N N N 个不超过 10000 10000 10000 的正整数,表示完整的数组。
输出格式
输出一个整数,表示最大利润。
数据范围
1 ≤ N ≤ 1 0 5 1 \le N \le 10^5 1 ≤ N ≤ 1 0 5 ,
1 ≤ k ≤ 100 1 \le k \le 100 1 ≤ k ≤ 100
输入样例 1:
输出样例 1:
输入样例 2:
输出样例 2:
样例解释
样例1:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
样例2:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。共计利润 4+3 = 7.
算法分析
初始化:
f[0][j][0/1] = -INF
,无效,0个物品却进行了j次交易,不可能
f[i][0][1] = -INF
,无效,没进行交易却持有物品,不可能
f[i][0][0] = 0
,有效,没进行交易,也未持有,收益0
先将f
全部设置为无效状态-INF
,再单独处理有效的起始状态f[i][0][0] = 0
入口为f[0][0][0]
注意:
1 2 f[i][j][0 ] = max (f[i - 1 ][j - 1 ][1 ] + w[i], f[i - 1 ][j][0 ]); f[i][j][1 ] = max (f[i - 1 ][j][0 ] - w[i], f[i - 1 ][j][1 ]);
终究要回归到状态转移的起点,第一支股票只有买,和不买这两个操作,一定不可能是卖和不卖的这两个操作,因此第一支股票如果买入时,必须按照一次交易处理 。否则如果第一次股票如果买入时,不按一次交易处理,也就代表着第一支股票卖出才算一次交易,也就代表着在第一支股票卖出之前还买了一支股票,显然是矛盾的。
时间复杂度 O ( n m ) O(nm) O ( nm )
空间复杂度 O ( m ) O(m) O ( m )
Solution
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> using namespace std;const int N = 1e5 + 10 , M = 110 ;int f[N][M][2 ];int money[N];int main () { int n, m; cin >> n >> m; for (int i = 1 ; i <= n; ++i) cin >> money[i]; memset (f, 0xc0 , sizeof f); for (int i = 0 ; i <= n; ++i) f[i][0 ][0 ] = 0 ; for (int i = 1 ; i <= n; ++i) { for (int j = 1 ; j <= m; ++j) { f[i][j][0 ] = max (f[i - 1 ][j][0 ], f[i - 1 ][j][1 ] + money[i]); f[i][j][1 ] = max (f[i - 1 ][j - 1 ][0 ] - money[i], f[i - 1 ][j][1 ]); } } int ans = 0 ; for (int j = 0 ; j <= m; ++j) ans = max (ans, f[n][j][0 ]); cout << ans; }
滚动数组优化空间
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 #include <iostream> #include <cstring> using namespace std;const int N = 1e5 + 10 , M = 110 ;int f[2 ][M][2 ];int money[N];int main () { int n, m; cin >> n >> m; for (int i = 1 ; i <= n; ++i) cin >> money[i]; memset (f, 0xc0 , sizeof f); f[0 ][0 ][0 ] = f[1 ][0 ][0 ] = 0 ; for (int i = 1 ; i <= n; ++i) { for (int j = 1 ; j <= m; ++j) { f[i & 1 ][j][0 ] = max (f[(i - 1 ) & 1 ][j][0 ], f[(i - 1 ) & 1 ][j][1 ] + money[i]); f[i & 1 ][j][1 ] = max (f[(i - 1 ) & 1 ][j - 1 ][0 ] - money[i], f[(i - 1 ) & 1 ][j][1 ]); } } int ans = 0 ; for (int j = 0 ; j <= m; ++j) ans = max (ans, f[0 ][j][0 ]); for (int j = 0 ; j <= m; ++j) ans = max (ans, f[1 ][j][0 ]); cout << ans; }
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 #include <iostream> #include <cstring> using namespace std;const int N = 1e5 + 10 , M = 110 ;int f[M][2 ];int money[N];int main () { int n, m; cin >> n >> m; for (int i = 1 ; i <= n; ++i) cin >> money[i]; memset (f, 0xc0 , sizeof f); f[0 ][0 ] = 0 ; for (int i = 1 ; i <= n; ++i) { for (int j = m; j >= 1 ; --j) { f[j][0 ] = max (f[j][0 ], f[j][1 ] + money[i]); f[j][1 ] = max (f[j - 1 ][0 ] - money[i], f[j][1 ]); } } int ans = 0 ; for (int j = 0 ; j <= m; ++j) ans = max (ans, f[j][0 ]); cout << ans; }
题目描述
给定一个长度为 N N N 的数组,数组中的第 i i i 个数字表示一个给定股票在第 i i i 天的价格。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 1 1 天)。
输入格式
第一行包含整数 N N N ,表示数组长度。
第二行包含 N N N 个不超过 10000 10000 10000 的正整数,表示完整的数组。
输出格式
输出一个整数,表示最大利润。
数据范围
1 ≤ N ≤ 1 0 5 1 \le N \le 10^5 1 ≤ N ≤ 1 0 5
输入样例 :
输出样例 :
样例解释
对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出],第一笔交易可得利润 2-1 = 1,第二笔交易可得利润 2-0 = 2,共得利润 1+2 = 3。
算法分析
初始化:f[0][2] = 0
,f[0][0] = f[0][1] = -INF
, 因为在第0
个股票一定是无货的,且可以买入,必定从2这个状态开始转移才有效
出口:可能最后一天才卖出,也可能最后一天是最后一次卖出操作好几天后了,故取max(f[n][1],f[n][2])
Solution
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> #include <cstring> using namespace std;const int N = 1e5 + 10 ;int money[N], f[N][3 ];int main () { int n; cin >> n; for (int i = 1 ; i <= n; ++i) cin >> money[i]; memset (f, 0xc0 , sizeof f); f[0 ][2 ] = 0 ; for (int i = 1 ; i <= n; ++i) { f[i][0 ] = max (f[i - 1 ][0 ], f[i - 1 ][2 ] - money[i]); f[i][1 ] = f[i - 1 ][0 ] + money[i]; f[i][2 ] = max (f[i - 1 ][1 ], f[i - 1 ][2 ]); } cout << max (f[n][1 ], f[n][2 ]); }
滚动数组优化空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> #include <cstring> using namespace std;const int N = 1e5 + 10 ;int money[N], f[2 ][3 ];int main () { int n; cin >> n; for (int i = 1 ; i <= n; ++i) cin >> money[i]; memset (f, 0xc0 , sizeof f); f[0 ][2 ] = 0 ; for (int i = 1 ; i <= n; ++i) { f[i & 1 ][0 ] = max (f[(i - 1 ) & 1 ][0 ], f[(i - 1 ) & 1 ][2 ] - money[i]); f[i & 1 ][1 ] = f[(i - 1 ) & 1 ][0 ] + money[i]; f[i & 1 ][2 ] = max (f[(i - 1 ) & 1 ][1 ], f[(i - 1 ) & 1 ][2 ]); } cout << max (f[n & 1 ][1 ], f[n & 1 ][2 ]); }
题目描述
你现在需要设计一个密码 S S S ,S S S 需要满足:
S S S 的长度是 N N N ;
S S S 只包含小写英文字母;
S S S 不包含子串 T T T ;
例如:a b c abc ab c 和 a b c d e abcde ab c d e 是 a b c d e abcde ab c d e 的子串,a b d abd ab d 不是 a b c d e abcde ab c d e 的子串。
请问共有多少种不同的密码满足要求?
由于答案会非常大,请输出答案模 1 0 9 + 7 10^9+7 1 0 9 + 7 的余数。
输入格式
第一行输入整数N,表示密码的长度。
第二行输入字符串T,T中只包含小写字母。
输出格式
输出一个正整数 ,表示总方案数模 1 0 9 + 7 10^9+7 1 0 9 + 7 后的结果。
数据范围
1 ≤ N ≤ 50 1 \le N \le 50 1 ≤ N ≤ 50 ,
1 ≤ ∣ T ∣ ≤ N 1 \le |T| \le N 1 ≤ ∣ T ∣ ≤ N ,∣ T ∣ |T| ∣ T ∣ 是T T T 的长度。
输入样例 1:
输出样例 1:
输入样例 2:
输出样例 2:
算法分析
字符串匹配自动机 + KMP详细解释见 一文吃透字符串匹配!
f[i][j]
:生成了i
个密码,且状态停留在j
,即有限自动机读入了i
个字符,且ϕ ( T i ) = j \phi(T_i)=j ϕ ( T i ) = j
状态j
也表示已成功匹配j
个字符
算法流程:
循环生成密码字符个数,正在生成第i
个字符
枚举生成i - 1
个密码字符后,所停留的状态 ϕ ( T i − 1 ) = j \phi(T_{i-1})=j ϕ ( T i − 1 ) = j
枚举正在生成的是什么字符,T [ i ] = a ∼ z T[i] = a \sim z T [ i ] = a ∼ z
计算状态转移,求 ϕ ( T i ) = δ ( j , T [ i ] ) \phi(T_i) = \delta(j,T[i]) ϕ ( T i ) = δ ( j , T [ i ]) ,转移函数用KMP算法的前缀函数 π ( ) \pi() π ( ) 来实现
状态转移从j
到q
,更新计算f[i][q]
的方案数
计算最终所有方案数,生成完n
个密码字符,且状态不停留在m
的方案都满足条件,累加起来
Solution
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> using namespace std;const int N = 60 , mod = 1e9 + 7 ;int f[N][N], ne[N];int main () { int n; cin >> n; string p; cin >> p; int m = p.size (); p = " " + p; for (int i = 2 , j = 0 ; i <= m; ++i) { while (j > 0 && p[i] != p[j + 1 ]) j = ne[j]; if (p[i] == p[j + 1 ]) ++j; ne[i] = j; } f[0 ][0 ] = 1 ; for (int i = 1 ; i <= n; ++i) { for (int j = 0 ; j < m; ++j) { for (char c = 'a' ; c <= 'z' ; ++c) { int q = j; while (q > 0 && c != p[q + 1 ]) q = ne[q]; if (c == p[q + 1 ]) ++q; if (q < m) f[i][q] = (f[i][q] + f[i - 1 ][j]) % mod; } } } int ans = 0 ; for (int j = 0 ; j < m; ++j) ans = (ans + f[n][j]) % mod; cout << ans; }
题目描述
生物学家终于发明了修复DNA的技术,能够将包含各种遗传疾病的DNA片段进行修复。
为了简单起见,DNA看作是一个由’A’, ‘G’ , ‘C’ , ‘T’构成的字符串。
修复技术就是通过改变字符串中的一些字符,从而消除字符串中包含的致病片段。
例如,我们可以通过改变两个字符,将DNA片段”AAGCAG”变为”AGGCAC”,从而使得DNA片段中不再包含致病片段”AAG”,”AGC”,”CAG”,以达到修复该DNA片段的目的。
需注意,被修复的DNA片段中,仍然只能包含字符’A’, ‘G’ , ‘C’ , ‘T’。
请你帮助生物学家修复给定的DNA片段,并且修复过程中改变的字符数量要尽可能的少。
输入格式
输入包含多组测试数据。
每组数据第一行包含整数N,表示致病DNA片段的数量。
接下来N行,每行包含一个长度不超过20的非空字符串,字符串中仅包含字符’A’, ‘G’ , ‘C’ , ‘T’,用以表示致病DNA片段。
再一行,包含一个长度不超过1000的非空字符串,字符串中仅包含字符’A’, ‘G’ , ‘C’ , ‘T’,用以表示待修复DNA片段。
最后一组测试数据后面跟一行,包含一个0,表示输入结束。
输出格式
每组数据输出一个结果,每个结果占一行。
输入形如”Case x: y”,其中x为测试数据编号(从1开始),y为修复过程中所需改变的字符数量的最小值,如果无法修复给定DNA片段,则y为”-1”。
数据范围
1 ≤ N ≤ 50 1 \le N \le 50 1 ≤ N ≤ 50
输入样例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 2 AAA AAG AAAG 2 A TG TGAATG 4 A G C T AGT 0
输出样例 :
1 2 3 Case 1: 1 Case 2: 4 Case 3: -1
算法分析
Solution