算法自学__线性动态规划
例1 P1020 [NOIP1999 普及组] 导弹拦截
题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
一行,若干个整数,中间由空格隔开。
输出格式
两行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
样例 #1
样例输入 #1
389 207 155 300 299 170 158 65
样例输出 #1
6
2
提示
对于前
50
%
50\%
50% 数据(NOIP 原题数据),满足导弹的个数不超过
1
0
4
10^4
104 个。该部分数据总分共
100
100
100 分。可使用
O
(
n
2
)
\mathcal O(n^2)
O(n2) 做法通过。
对于后
50
%
50\%
50% 的数据,满足导弹的个数不超过
1
0
5
10^5
105 个。该部分数据总分也为
100
100
100 分。请使用
O
(
n
log
n
)
\mathcal O(n\log n)
O(nlogn) 做法通过。
对于全部数据,满足导弹的高度为正整数,且不超过 5 × 1 0 4 5\times 10^4 5×104。
思路
这道题目实质上就是考察最长不上升子序列和最长上升子序列的求法,以前者为例。
定义tail1[i]
,表示:长度为i
的不上升子序列的最后一个元素的值。显然,tail1[]
数组是单调不升的。定义cnt1
表示当前最长不上升子序列的长度。我们从头至尾遍历输入的每一个元素,如果当前元素:
-
≤
\leq
≤
tail1[cnt1]
,说明当前元素可以直接添加到目前的最长不上升子序列的末尾,形成一个更长的子序列。 -
>
>
>
tail1[cnt2]
,我们二分查找到tail1[]
数组中第一个小于当前元素的值tail1[i]
,并将其修改为当前元素。首先,这样做是可行的,因为tail1[i-1]
一定 ≥ \ge ≥ 当前元素,所以将当前元素接在一个长度为i-1
的不上升子序列的结尾,可以形成一个长度为i
的不上升子序列,符合tail1[]
数组的定义。其次,这样做是合理的,因为在一个不上升子序列中,位于前面的元素越大,这个子序列便可能越长。
遍历输入的所有元素后,cnt1
的值即为最长不上升子序列的长度。
代码
#include<bits/stdc++.h>
#define maxn 100005
using namespace std;
int a[maxn];
int n = 0;
int tail1[maxn];
int tail2[maxn];
int cnt1=0, cnt2=0;
int main(){
int temp;
while(cin>>temp){
n++;
a[n] = temp;
}
tail1[0] = 0x7f7f7f7f;
tail2[0] = -1;
for(int i=1;i<=n;i++){
if(a[i]<=tail1[cnt1]){
cnt1++;
tail1[cnt1] = a[i];
}
else{
int L=1, R=cnt1, pos=0;
while(L<=R){
int mid = (L+R)>>1;
if(tail1[mid]<a[i]){
pos = mid;
R = mid-1;
}
else{
L = mid+1;
}
}
tail1[pos] = a[i];
}
}
for(int i=1;i<=n;i++){
if(a[i]>tail2[cnt2]){
cnt2++;
tail2[cnt2] = a[i];
}
else if(a[i]==tail2[cnt2]){
continue;
}
else{
int L=1, R=cnt2, pos=0;
while(L<=R){
int mid = (L+R)>>1;
if(tail2[mid]>=a[i]){
pos = mid;
R = mid-1;
}
else{
L = mid+1;
}
}
tail2[pos] = a[i];
}
}
cout<<cnt1<<'\n'<<cnt2;
return 0;
}
例2 P2679 [NOIP2015 提高组] 子串
题目描述
有两个仅包含小写英文字母的字符串 A A A 和 B B B。
现在要从字符串 A A A 中取出 k k k 个互不重叠的非空子串,然后把这 k k k 个子串按照其在字符串 A A A 中出现的顺序依次连接起来得到一个新的字符串。请问有多少种方案可以使得这个新串与字符串 B B B 相等?
注意:子串取出的位置不同也认为是不同的方案。
输入格式
第一行是三个正整数 n , m , k n,m,k n,m,k,分别表示字符串 A A A 的长度,字符串 B B B 的长度,以及问题描述中所提到的 k k k,每两个整数之间用一个空格隔开。
第二行包含一个长度为 n n n 的字符串,表示字符串 A A A。
第三行包含一个长度为 m m m 的字符串,表示字符串 B B B。
输出格式
一个整数,表示所求方案数。
由于答案可能很大,所以这里要求输出答案对 1000000007 1000000007 1000000007 取模的结果。
样例 #1
样例输入 #1
6 3 1
aabaab
aab
样例输出 #1
2
提示
对于第 1 组数据:
1
≤
n
≤
500
,
1
≤
m
≤
50
,
k
=
1
1≤n≤500,1≤m≤50,k=1
1≤n≤500,1≤m≤50,k=1;
对于第 2 组至第 3 组数据:
1
≤
n
≤
500
,
1
≤
m
≤
50
,
k
=
2
1≤n≤500,1≤m≤50,k=2
1≤n≤500,1≤m≤50,k=2;
对于第 4 组至第 5 组数据:
1
≤
n
≤
500
,
1
≤
m
≤
50
,
k
=
m
1≤n≤500,1≤m≤50,k=m
1≤n≤500,1≤m≤50,k=m;
对于第 1 组至第 7 组数据:
1
≤
n
≤
500
,
1
≤
m
≤
50
,
1
≤
k
≤
m
1≤n≤500,1≤m≤50,1≤k≤m
1≤n≤500,1≤m≤50,1≤k≤m;
对于第 1 组至第 9 组数据:
1
≤
n
≤
1000
,
1
≤
m
≤
100
,
1
≤
k
≤
m
1≤n≤1000,1≤m≤100,1≤k≤m
1≤n≤1000,1≤m≤100,1≤k≤m;
对于所有 10 组数据:
1
≤
n
≤
1000
,
1
≤
m
≤
200
,
1
≤
k
≤
m
1≤n≤1000,1≤m≤200,1≤k≤m
1≤n≤1000,1≤m≤200,1≤k≤m。
思路
定义状态f[i][j][k]
表示:在字符串a
的前i
个字符中任意选取k
个子串,且必须选择a[i]
,能拼出字符串b
的前j
个字符构成的子串的方案数。
定义状态g[i][j][k]
表示:在字符串a
的前i
个字符中任意选取k
个子串,a[i]
可选可不选,能拼出字符串b
的前j
个字符构成的子串的方案数。
状态转移方程一:
f
[
i
]
[
j
]
[
k
]
=
{
f
[
i
−
1
]
[
j
−
1
]
[
k
]
+
g
[
i
−
1
]
[
j
−
1
]
[
k
−
1
]
,
a
[
i
]
=
=
b
[
j
]
0
,
a
[
i
]
!
=
b
[
j
]
f[i][j][k] = \begin{cases} f[i-1][j-1][k] + g[i-1][j-1][k-1], &a[i]==b[j]\\ 0, &a[i]\ !=b[j] \end{cases}
f[i][j][k]={f[i−1][j−1][k]+g[i−1][j−1][k−1],0,a[i]==b[j]a[i] !=b[j]
解释:当a[i]
和b[i]
不相等时,f[i][j][k]
显然为0。当a[i]
和b[i]
相等时,a[i]
要么和别的字符共同构成一个子串,要么自己单独构成一个子串。和别的字符共同构成一个子串的情形,对应f[i-1][j-1][k]
,即a[i]
添加到了a[i-1]
所在子串的末尾;单独构成一个子串的情形,对应g[i-1][j-1][k-1]
,即前面选出k-1
个子串,构成b[1]
~b[j-1]
的部分。
状态转移方程二:
g
[
i
]
[
j
]
[
k
]
=
f
[
i
]
[
j
]
[
k
]
+
g
[
i
−
1
]
[
j
]
[
k
]
g[i][j][k] = f[i][j][k] + g[i-1][j][k]
g[i][j][k]=f[i][j][k]+g[i−1][j][k]
解释:根据g[i][j][k]
的定义,a[i]
要么选,要么不选。选的情形,对应f[i][j][k]
;不选的情形,对应g[i-1][j][k]
。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1005;
const int maxm = 205;
const int p = 1000000007;
int f[2][maxm][maxm];
int g[2][maxm][maxm];
int cnt = 0;
int n, m, K;
char a[maxn], b[maxm];
int main(){
cin>>n>>m>>K;
//让字符串从下标1开始
cin>>a+1>>b+1;
//保证(a[i]==b[j])为真,且j为1、k为1时,f[cnt][j][k]为1
g[0][0][0] = g[1][0][0] = 1;
//滚掉i
for(int i=1;i<=n;i++){
for(int j=1;j<=min(m, i);j++){
for(int k=1;k<=K;k++){
if(a[i]==b[j]){
f[cnt][j][k] = (f[cnt^1][j-1][k]+g[cnt^1][j-1][k-1]) % p;
}
else{
f[cnt][j][k] = 0;
}
g[cnt][j][k] = (g[cnt^1][j][k]+f[cnt][j][k]) % p;
}
}
cnt = cnt^1;
}
cout<<g[cnt^1][m][K];
return 0;
}