股票买卖
股票买卖Ⅱ
题意: 给定一个长度为
N
N
N的数组,数组中的第
i
i
i 个数字表示一个给定股票在第
i
i
i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
- sol:
定义两个状态:未持股票0,持股票1
用 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i天,第 i i i天持股状态为 j ∈ ( 0 , 1 ) j\in (0, 1) j∈(0,1)的所有方案的最大收益
未持股票状态:
1.0->1买入 2.0->0继续观望
持股票状态:
1.1->0卖出 2.1->1继续观望
状态计算:
f [ i ] 0 ] = m a x ( f [ i − 1 ] [ 0 ] , f [ i − 1 ] [ 1 ] + w [ i ] ) f [ i ] [ 1 ] = m a x ( f [ i − 1 ] [ 1 ] , f [ i − 1 ] [ 0 ] − w [ i ] ) f[i]0] = max(f[i - 1][0], f[i-1][1] + w[i])\\ f[i][1] = max(f[i-1][1], f[i-1][0]-w[i]) f[i]0]=max(f[i−1][0],f[i−1][1]+w[i])f[i][1]=max(f[i−1][1],f[i−1][0]−w[i])
具体细节看代码:
code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, f[2][2], w[N]; //滚动数组优化空间(其实没必要hh
int main()
{
int n; cin >> n;
for(int i = 1; i <= n; ++ i) {
cin >> w[i];
}
f[0][1] = -0x3f3f3f3f;// 由于第一天不能卖出,所以要设置0天持股状态的最大收益为负无穷
for(int i = 1; i <= n; ++ i) {
f[i & 1][0] = max(f[i - 1 & 1][0], f[i - 1 & 1][1] + w[i]);
f[i & 1][1] = max(f[i - 1 & 1][1], f[i - 1 & 1][0] - w[i]);
}
cout << f[n & 1][0] << "\n";
}
股票买卖Ⅳ
题目链接:Acwing 1057
交易次数变成了至多
k
k
k 次
- sol:
定义 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]为前 i i i天,完成了 j j j笔完整交易,第 i i i天的决策是 k ∈ ( 0 , 1 ) k\in(0,1) k∈(0,1)的所有方案的最大收益
状态计算:
f [ i ] [ j ] [ 0 ] = m a x ( f [ i − 1 ] [ j ] [ 0 ] , f [ i − 1 ] [ j − 1 ] [ 1 ] + w [ i ] ) f [ i ] [ j ] [ 1 ] = m a x ( f [ i − 1 ] [ j ] [ 1 ] , f [ i − 1 ] [ j ] [ 0 ] − w [ i ] ) f[i][j][0] = max(f[i-1][j][0],f[i-1][j-1][1] +w[i])\\ f[i][j][1]=max(f[i-1][j][1],f[i-1][j][0]-w[i]) f[i][j][0]=max(f[i−1][j][0],f[i−1][j−1][1]+w[i])f[i][j][1]=max(f[i−1][j][1],f[i−1][j][0]−w[i])
code:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int f[2][110][2];
int n, k;
int main()
{
cin >> n >> k;
memset(f, -0x3f, sizeof f);
f[0][0][0] = 0;
for(int i = 1; i <= n; ++ i) {
int x; cin >> x;
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] + x);
f[i & 1][j][1] = max(f[i - 1 & 1][j][0] - x, f[i - 1 & 1][j][1]);
}
}
int res = 0;
for(int i = 0; i <= k; ++ i) res = max(res, f[n & 1][i][0]);
cout << res << "\n";
}
股票买卖 V
题目链接:Acwing 1059
在Ⅱ的条件下,增加了冷冻期:卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)
- sol:
在Ⅱ的情况下,增加一个状态冷冻期,如下:
定义三个状态:未持股票0,持股票1,冷冻期2
用 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i天,第 i i i天持股状态为 j ∈ ( 0 , 1 , 2 ) j\in (0, 1, 2) j∈(0,1,2)的所有方案的最大收益
未持股票状态:
1.0->1买入 2.0->0继续观望
持股票状态:
1.1->2卖出 2.1->1继续观望
冷冻期状态:
2->0回到未持股状态
状态计算:
f [ i ] 0 ] = m a x ( f [ i − 1 ] [ 0 ] , f [ i − 1 ] [ 2 ] ) f [ i ] [ 1 ] = m a x ( f [ i − 1 ] [ 1 ] , f [ i − 1 ] [ 0 ] − w [ i ] ) f [ i ] [ 2 ] = f [ i − 1 ] [ 1 ] + w [ 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] 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]
code:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int f[2][3], n;
int main()
{
cin >> n;
f[0][1] = -0x3f3f3f3f;
for(int i = 1; i <= n; ++ i) {
int x; cin >> x;
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] - x);
f[i & 1][2] = max(f[i - 1 & 1][2], f[i - 1 & 1][1] + x);
}
cout << max(f[n & 1][0], f[n & 1][2]) << "\n";
}
股票买卖 VI
题目链接:Acwing 1059
在Ⅱ的基础上,每次买卖有手续费
f
f
f
- sol:
与股票买卖Ⅱ同样定义,注意在卖出的时候算上手续费即可
code:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, f[2][2], w[N]; //滚动数组优化空间(其实没必要hh
int main()
{
int n, F; cin >> n >> F;
for(int i = 1; i <= n; ++ i) {
cin >> w[i];
}
f[0][1] = -0x3f3f3f3f;// 由于第一天不能卖出,所以要设置0天持股状态的最大收益为负无穷
for(int i = 1; i <= n; ++ i) {
f[i & 1][0] = max(f[i - 1 & 1][0], f[i - 1 & 1][1] + w[i] - F);
f[i & 1][1] = max(f[i - 1 & 1][1], f[i - 1 & 1][0] - w[i]);
}
cout << f[n & 1][0] << "\n";
}
DP+KMP
设计密码
题目链接:Acwing 1052
题意:
你现在需要设计一个密码
S
S
S,
S
S
S 需要满足:
- S S S 的长度是 N N N;
- S S S 只包含小写英文字母;
- S S S 不包含子串 T T T;
请问共有多少种不同的密码满足要求?答案对 1 e 9 + 7 1e9+7 1e9+7取模
对于此题,有两个扩展,一个是将
n
n
n扩大到了
1
0
9
10^9
109,一个是将子串数量增加。分别对应下文的GT考试和修复DNA
solution:
假设已经成功构造了长度为
i
i
i的密码,在构造
i
+
1
i+1
i+1位的时候,如果出现了子串
T
T
T,那么一定是密码的后缀当中出现了子串
T
T
T。然后就可以进行愉快的 DP了
- 状态定义: f [ i ] [ j ] f[i][j] f[i][j] 表示成功构造了密码为 i i i位,且后缀中匹配到子串位置是 j j j的方案数
- 状态计算: f [ i + 1 ] [ j ] + = f [ i ] [ j ] f[i+1][j] += f[i][j] f[i+1][j]+=f[i][j](这里是枚举了第 i + 1 i+1 i+1位的字母,然后去进行kmp匹配,具体看代码)
code:
#include <bits/stdc++.h>
using namespace std;
const int N = 55, mod = 1e9 + 7;
char s[N];
int f[N][N], ne[N], n;
int main()
{
cin >> n >> s + 1;
int 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 now = j; // 对每个状态,在后面加上字符ch后,进行一下kmp匹配
while(now && s[now + 1] != ch) now = ne[now];
if(s[now + 1] == ch) ++ now;
if(now < m) { // 没有匹配到子串
f[i + 1][now] = (f[i + 1][now] + f[i][j]) % mod;
}
}
}
}
int res = 0;
for(int i = 0; i < m; ++ i) res = (res +f[n][i]) % mod;
cout << res << "\n";
}
DP+AC自动机
文本生成器
题意:
有
n
n
n个单词,问长度为
m
m
m的文章中包括这些单词的数量。答案对
1
0
4
+
7
10^4+7
104+7取模。仅包含大写字母
solution:
答案容易转化为:
2
6
m
−
26^m-
26m−长度为
m
m
m的文章不包含这个
n
n
n单词
如何求长度为
m
m
m的文章不包含这个
n
n
n单词:
上kmp相似,将
n
n
n个单词构建ac自动机(trie图)。
- 状态定义: f [ i ] [ j ] f[i][j] f[i][j]表示长度为 i i i的文章,在trie图上的状态为 j j j的方案数
- 状态计算: f [ i + 1 ] [ p ] + = f [ i ] [ j ] , p = t r i e [ j ] [ k ] f[i+1][p] += f[i][j], p = trie[j][k] f[i+1][p]+=f[i][j],p=trie[j][k]具体看代码
code:
#include <bits/stdc++.h>
using namespace std;
const int N = 6010, M = 110, mod = 10007;
int n, m;
int trie[N][26], fail[N], idx;
bool used[N];
char str[M];
int f[M][N];
// 构建AC自动机
void insert() {
int p = 0;
for(int i = 0; str[i]; ++ i) {
int u = str[i] - 'A';
if(!trie[p][u]) trie[p][u] = ++ idx;
p = trie[p][u];
}
used[p] = true;
}
void build() {
queue<int> q;
for(int i = 0; i < 26; ++ i) {
if(trie[0][i]) q.push(trie[0][i]);
}
while(q.size()) {
auto t = q.front(); q.pop();
for(int i = 0; i < 26; ++ i) {
int p = trie[t][i];
if(!p) trie[t][i] = trie[fail[t]][i];
else {
q.push(p);
fail[p] = trie[fail[t]][i];
used[p] |= used[fail[p]];
}
}
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; ++ i ){
cin >> str;
insert();
}
build();
f[0][0] = 1; // 初始化
for(int i = 0; i < m; ++ i) {
for(int j = 0; j <= idx; ++ j) { // 枚举ac自动机的所有状态进行更新
for(int k = 0; k < 26; ++ k) { // 枚举所有结点
int p = trie[j][k];
if(used[p]) continue; // 如果有单词则直接跳过
f[i + 1][p] = (f[i + 1][p] + f[i][j]) % mod; // 更新答案
}
}
}
int res = 1, del = 0;;
for(int i = 1; i <= m; ++ i) res = res * 26 % mod;
for(int i = 0; i <= idx; ++ i) del = (del + f[m][i]) % mod;
cout << (res - del + mod) % mod;
}
修复DNA
题目链接:Acwing 1053
题意:
有
n
n
n个有害DNA序列(长度不超过20),然后给出一个DNA序列(长度不超过1000)。DNA序列只包含(‘A’,‘G’,‘C’,‘T’)问将这个DNA序列修复成不含有有害DNA要改变的最少字符。
solution:
遇上题dp定义差不多,将构造改成了修改。假设已经修改好了
i
i
i位,在修改第
i
+
1
i+1
i+1位的时候,进行状态转移
- 状态表示: f [ i ] [ j ] f[i][j] f[i][j] 表示 长度为 i i i的DNA片段,在trie图当中的状态为 j j j的最小操作数
- 状态计算: f [ i + 1 ] [ p ] = m i n ( f [ i + 1 ] [ p ] , f [ i ] [ j ] + k = = s t r [ i + 1 ] ) ( k ∈ ( A , G , C , T ) f[i + 1][p] = min(f[i+1][p], f[i][j]+k == str[i+1])(k \in (A,G,C,T) f[i+1][p]=min(f[i+1][p],f[i][j]+k==str[i+1])(k∈(A,G,C,T) 具体看代码
code:
#include <bits/stdc++.h>
using namespace std;
const int N = 1010, M = 22 * 55;
char s[M], str[N];
int f[N][N];
int tr[M][5], fail[M], idx;
bool used[M];
int n, m;
int get(char ch) {
if(ch == 'A') return 1;
if(ch == 'C') return 2;
if(ch == 'G') return 3;
if(ch == 'T') return 4;
}
// AC自动机模板
void insert()
{
int p = 0;
for(int i = 0; s[i]; ++ i) {
int u = get(s[i]);
if(!tr[p][u]) tr[p][u] = ++ idx;
p = tr[p][u];
}
used[p] = true;
}
void build()
{
queue<int> q;
for(int i = 1; i <= 4; ++ i) {
if(tr[0][i]) q.push(tr[0][i]);
}
while(q.size())
{
int t = q.front(); q.pop();
for(int i = 1; i <= 4; ++ i) {
int p = tr[t][i];
if(!p) tr[t][i] = tr[fail[t]][i];
else {
fail[p] = tr[fail[t]][i];
used[p] |= used[fail[p]]; // 如果一个结点不是“有害”结点,但是他的fail数组指向了“有害”结点,
//说明他的后缀存在有害DANA
q.push(p);
}
}
}
}
int main()
{
int Cas = 1;
while(cin >> n && n) {
memset(tr, 0, sizeof tr); idx = 0;
memset(fail, 0, sizeof fail);
memset(used, 0, sizeof used);
memset(f, 0x3f, sizeof f);
for(int i= 1; i <= n; ++ i) {
cin >> s;
insert();
}
build();
cin >> str + 1;
m = strlen(str + 1);
f[0][0] = 0; // 初始化
for(int i = 0; i < m; ++ i) {
for(int j = 0; j <= idx; ++ j) { // 枚举一下trie图的所有结点(状态)
for(int k = 1; k <= 4; ++ k) {
int p = tr[j][k]; // 当前结点 不是有害的
if(used[p]) continue; // 进行状态转移,若干原DNA不是k,则需+1
f[i + 1][p] = min(f[i + 1][p], f[i][j] + (k != get(str[i + 1])));
}
}
}
int res = 0x3f3f3f3f;
for(int i = 0; i <= idx; ++ i) res = min(res, f[m][i]);
cout << "Case " << Cas ++ << ": ";
cout << (res == 0x3f3f3f3f ? -1 : res) << "\n";
}
}
与矩阵快速幂的结合
GT考试
前置题目是:上文的设计密码
题目链接:Acwing 1305
题意与设计密码类似,求出长度为
n
n
n的序列中不包括子串的所有方案。注意这里的
n
n
n达到了
1
0
9
10^9
109
分析:同设计密码定义
f
(
i
,
j
)
f(i, j)
f(i,j)。能够发现:
f
(
i
+
1
,
0
)
=
a
0
,
0
∗
f
(
i
,
0
)
+
a
1
,
0
∗
f
(
i
,
1
)
,
+
⋯
+
,
a
m
−
1
,
0
∗
f
(
i
,
m
−
1
)
⋯
f
(
i
+
1
,
m
−
1
)
=
a
0
,
m
−
1
∗
f
(
i
,
0
)
+
a
1
,
m
−
1
∗
f
(
i
,
1
)
,
+
⋯
+
,
a
m
−
1
,
m
−
1
∗
f
(
i
,
m
−
1
)
f(i+1, 0) = a_{0,0}*f(i,0)+a_{1,0}*f(i, 1),+\cdots+, a_{m-1,0}*f(i,m-1)\\ \cdots \\ f(i+1, m-1)=a_{0,m-1}*f(i,0)+a_{1,m-1}*f(i, 1),+\cdots+, a_{m-1,m-1}*f(i,m-1)
f(i+1,0)=a0,0∗f(i,0)+a1,0∗f(i,1),+⋯+,am−1,0∗f(i,m−1)⋯f(i+1,m−1)=a0,m−1∗f(i,0)+a1,m−1∗f(i,1),+⋯+,am−1,m−1∗f(i,m−1)
其中,
a
i
,
j
a_{i,j}
ai,j为,在向下一位计算的时候,是否能从
i
i
i状态转移到
j
j
j状态,也就是说对于一个子串来说,
a
i
.
j
a_{i.j}
ai.j是固定的。
也就是:
f
(
i
+
1
)
=
f
(
i
)
∗
A
f(i+1) = f(i) * A
f(i+1)=f(i)∗A
A
=
[
a
0
,
0
a
0
,
1
⋯
a
0
,
m
−
1
a
1
,
0
a
1
,
1
⋯
a
1
,
m
−
1
⋯
⋯
⋯
⋯
a
m
−
1
,
0
a
m
−
1
,
1
⋯
a
m
−
1
,
m
−
1
]
A=\left[ \begin{matrix} a_{0,0} & a_{0,1} & \cdots & a_{0,m-1} \\ a_{1,0} & a_{1,1} & \cdots &a_{1,m-1} \\ \cdots & \cdots &\cdots&\cdots \\ a_{m-1,0} &a_{m-1,1} & \cdots &a_{m-1,m-1} \end{matrix} \right]
A=⎣⎢⎢⎡a0,0a1,0⋯am−1,0a0,1a1,1⋯am−1,1⋯⋯⋯⋯a0,m−1a1,m−1⋯am−1,m−1⎦⎥⎥⎤
要求长度为
n
n
n的只需求:
f
(
0
)
∗
A
n
f(0) * A^n
f(0)∗An
f
(
0
)
=
(
1
,
0
,
⋯
,
0
)
f(0) = (1, 0, \cdots,0)
f(0)=(1,0,⋯,0)
矩阵快速幂知识
具体来看代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 25;
char str[N];
int ne[N];
int a[N][N], base[N][N];
int n, m, mod;
// 矩阵乘法
void mul(int a[][N], int b[][N])
{
static int c[N][N];
memset(c, 0, sizeof c);
for(int i = 0; i < N; ++ i) {
for(int j = 0; j < N; ++ j) {
for(int k = 0; k < N; ++ k) {
c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % mod;
}
}
}
memcpy(a, c, sizeof c);
}
int qpow(int n)
{
// 构建base即A矩阵
for(int j = 0; j < m; ++ j) {
for(char ch = '0'; ch <= '9'; ++ ch) {
int now = j;
while(now && str[now + 1] != ch) now = ne[now];
if(str[now + 1] == ch) ++ now;
if(now < m) base[j][now] ++ ;
}
}
a[0][0] = 1; // 初始F(0)
while(n) {
if(n & 1) mul(a, base);
n >>= 1;
mul(base, base);
}
int res = 0; // 统计答案
for(int i = 0; i < m; ++ i) res = (res + a[0][i]) % mod;
return res;
}
int main()
{
cin >> n >> m >> mod;
cin >> str + 1;
// kmp预处理
for(int i = 2, j = 0; str[i]; ++ i) {
while(j && str[i] != str[j + 1]) j = ne[j];
if(str[i] == str[j + 1]) ++ j;
ne[i] = j;
}
cout << qpow(n) << "\n";
}
DNA Sequence
题目链接:POJ 2778
题意:
给出
m
m
m个有害DNA序列,问长度为
n
n
n的DNA序列,不包含有害DNA序列的数量。答案对
1
0
5
10^5
105取模
n
≤
2
×
1
0
9
,
m
≤
10
n \le 2×10^9,m\le10
n≤2×109,m≤10
solution:
根据
m
m
m个有害DNA序列,作出AC自动机的trie图。根据trie图作出可达矩阵。
对于可达矩阵,作
n
n
n次方后
a
i
,
j
a_{i,j}
ai,j的值就是从
i
i
i号结点出发,走
j
j
j步的方案数。 具体细节看代码
code:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#define endl '\n'
#define ALL(a) (a).begin(), (a).end()
#define IOS ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
using namespace std;
inline void Max(int &a, int b) { if(a < b) a = b; }
inline void Min(int &a, int b) { if(a > b) a = b; }
typedef long long LL;
const int N = 110, Mod = 100000;
char str[N];
int trie[N][4], fail[N], idx;
bool used[N];
int a[N][N], base[N][N], n, m;
int get(char s) {
if(s == 'A') return 0;
if(s == 'G') return 1;
if(s == 'C') return 2;
if(s == 'T') return 3;
}
// 构建trie图
void insert() {
int p = 0;
for(int i = 0; str[i]; ++ i) {
int u = get(str[i]);
if(!trie[p][u]) trie[p][u] = ++ idx;
p = trie[p][u];
}
used[p] = true;
}
void build() {
queue<int> q;
for(int i = 0; i < 4; ++ i) {
if(trie[0][i]) q.push(trie[0][i]);
}
while(q.size()) {
int t = q.front(); q.pop();
for(int i = 0; i < 4; ++ i) {
int &p = trie[t][i];
if(!p) p = trie[fail[t]][i];
else {
q.push(p);
fail[p] = trie[fail[t]][i];
used[p] |= used[fail[p]];
}
}
}
}
// 矩阵乘法
void mul(int a[][N], int b[][N]) {
static int c[N][N];
memset(c, 0, sizeof c);
// 这里如果是 < N 会TLE (很神奇
for(int i = 0; i <= idx; ++ i) {
for(int j = 0; j <= idx; ++ j) {
for(int k = 0; k <= idx; ++ k) {
c[i][j] = (c[i][j] + (LL)a[i][k] * b[k][j]) % Mod;
}
}
}
memcpy(a, c, sizeof c);
}
void solve()
{
cin >> m >> n;
for(int i = 1; i <= m; ++ i) {
cin >> str;
insert();
}
build();
// 写出可达矩阵
for(int i = 0; i <= idx; ++ i) {
for(int j = 0; j < 4; ++ j) {
int p = trie[i][j];
if(!used[i] && !used[p]) base[i][p] ++; // 当且仅当i和p结点都是无害的那么他们之间就可达
}
}
a[0][0] = 1;// 初始化
while(n) {// 矩阵快速幂
if(n & 1) mul(a, base);
n >>= 1;
mul(base, base);
}
int res = 0; // 累计答案
for(int i = 0; i <= idx; ++ i) res = (res + a[0][i]) % Mod;
cout << res << "\n";
}
int main(){
IOS;
int T = 1;
while(T --) solve();
return 0;
}