目录
模板:
DP有个毛线的模板
参考自yxc大佬。
主要问题是要确定状态转移方程。
根据闫式DP分析法,我们先确定状态表示:①所属的集合②属性
然后考虑状态计算:确定集合的划分(即确定最优子结构)。
注意当前结构可以由那些结构组成(即集合如何划分),放在题中具体讨论 。
例题:
AcWing 895 最长上升子序列
题干:
给定一个长度为N的数列,求数值严格单调递增的子序列的长度最长是多少。
样例输入:7
3 1 2 1 8 5 6
样例输出:4
思路:
第一步,确定状态表示:我们首先假设一个数组x[],保证x[]是递增的。
第二步,考虑状态计算:考虑当前数a[i]作为x[]数组的结尾,然后遍历i之前的数,会出现两种情况:
①:a[j]>=a[i](无法放入)
②:a[j]<a[i](可以放入)
这样x[i]代表的就是以a[i]为结尾的最长上升子序列长度。
然后可以得到转移方程
x
[
i
]
=
m
a
x
(
x
[
i
]
,
x
[
j
]
+
1
(
1
<
=
j
<
i
,
a
[
i
]
>
a
[
j
]
)
)
x[i]=max(x[i],{x[j]+1}(1<=j<i ,a[i]>a[j]))
x[i]=max(x[i],x[j]+1(1<=j<i,a[i]>a[j]))
#include<iostream>
#include <cmath>
#include <algorithm>
#include <cstdio>
using namespace std;
int a[1010],x[1010];
int main()
{
int n,mx=0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
x[i]=1;//注意初始化
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(a[i]>a[j])
x[i]=max(x[i],x[j]+1); //状态转移方程
}
}
for(int i=1;i<=n;i++)
mx=max(mx,x[i]);
printf("%d\n",mx);
return 0;
}
AcWing 897最长公共子序列
题干
给定两个长度分别为N和M的字符串A和B,求既是A的子序列又是B的子序列的字符串长度最长是多少。
样例输入:4 5
acbd
abedc
样例输出:3
思路:
第一步,确定状态表示:用x[i][j]表示A[1~ i]和B[1 ~ j]的最长公共子序列
第二步,考虑状态计算:比较a[i]和b[j]
①a[i]==b[j] ,则x[i][j]的结尾是a[i],也就是从x[i-1][j-1]这种情况+1。
②a[i]!=b[j],则x[i][j]的结尾应为a[i]或b[j],即x[i-1][j]或x[i][j-1]
然后从上述情况取一个最大值。
x
[
i
]
[
j
]
=
m
a
x
(
(
x
[
i
−
1
]
[
j
]
)
,
(
x
[
i
]
[
j
−
1
]
)
,
(
x
[
i
−
1
]
[
j
−
1
]
当
a
[
i
]
=
=
b
[
j
]
时
)
)
x[i][j]=max((x[i-1][j]),(x[i][j-1]),(x[i-1][j-1]当a[i]==b[j]时))
x[i][j]=max((x[i−1][j]),(x[i][j−1]),(x[i−1][j−1]当a[i]==b[j]时))
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;
char a[1010],b[1010];
int x[1010][1010];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
scanf("%s",a+1);
scanf("%s",b+1);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
x[i][j]=max(x[i-1][j],x[i][j-1]);
if(a[i]==b[j])
x[i][j]=max(x[i][j],x[i-1][j-1]+1);
//printf("%d ",x[i][j]);
}
//cout<<endl;
}
printf("%d\n",x[n][m]);
return 0;
}
AcWing 272 最长公共上升子序列
题干
对于两个数列A和B,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。求最长公共上升子序列的长度。
样例输入:4
2 2 1 3
2 1 2 3
样例输出:2
思路
题意可分成两部分,最长公共和最长上升
第一步,确定状态表示:用x[i][j]表示A[1~ i]和B[1 ~ j]的以b[j]为结尾的最长公共上升子序列
第二步,考虑状态计算:首先要公共才能排上升
①a[i]==b[k] 按照求最长上升的思路,
x
[
i
]
[
j
]
=
m
a
x
(
x
[
i
]
[
j
]
,
x
[
i
−
1
]
[
k
]
+
1
(
1
<
=
k
<
j
,
a
[
i
]
>
b
[
k
]
)
)
x[i][j]=max({x[i][j],x[i-1][k]+1}(1<=k<j ,a[i]>b[k]))
x[i][j]=max(x[i][j],x[i−1][k]+1(1<=k<j,a[i]>b[k]))
②a[i]!=b[k] 也就是不公共,因为我们是以b[j]为结尾的,所以
x
[
i
]
[
j
]
=
x
[
i
−
1
]
[
j
]
x[i][j]=x[i-1][j]
x[i][j]=x[i−1][j]
#include<cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
int a[3010],b[3010];
int x[3010][3010];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i]==b[j]){
for(int k=1;k<j;k++){
if(a[i]>b[k])
x[i][j]=max(x[i][j],x[i-1][k]+1);
}
}
else
x[i][j]=x[i-1][j];
}
}
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,x[n][i]);
printf("%d\n",ans);
return 0;
}
但是目前这个是o(
n
3
n^3
n3)所以我们需要一些优化。
考虑到
x
[
i
]
[
j
]
=
m
a
x
(
x
[
i
]
[
j
]
,
x
[
i
−
1
]
[
k
]
+
1
)
x[i][j]=max(x[i][j],x[i-1][k]+1)
x[i][j]=max(x[i][j],x[i−1][k]+1)仅在a[i]>b[k]时执行,而k是随着j每次加1的,而且在j的循环中a[i]是不会改变的,k的循环是为了寻找小于a[i]最大的b[k];所以当j+时,b[1~j]已经遍历过了,我们只需要比较a[i]和b[j+1]。这样我们就可以把k的循环拿到j的循环中去。
#include<cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
int a[3010],b[3010];
int x[3010][3010];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
for(int i=1;i<=n;i++){
int mx=1;
for(int j=1;j<=n;j++){
x[i][j]=x[i-1][j];
if(a[i]==b[j])
x[i][j]=max(x[i][j],mx);
if(a[i]>b[j])
mx=max(mx,x[i-1][j]+1);
}
}
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,x[n][i]);
printf("%d\n",ans);
return 0;
}
AcWing 1016最长上升子序列和
题干:
个数的序列
b
i
bi
bi,当
b
1
<
b
2
<
…
<
b
S
b_1<b_2<…<b_S
b1<b2<…<bS的时候,我们称这个序列是上升的。
对于给定的一个序列(
a
1
,
a
2
,
…
,
a
N
a_1,a_2,…,a_N
a1,a2,…,aN),我们可以得到一些上升的子序列(
a
i
1
,
a
i
2
,
…
,
a
i
K
a_{i1},a_{i2},…,a_{iK}
ai1,ai2,…,aiK),这里
1
≤
i
1
<
i
2
<
…
<
i
K
≤
N
1≤i_1<i_2<…<i_K≤N
1≤i1<i2<…<iK≤N。
比如,对于序列(1,7,3,5,9,4,8),有它的一些上升子序列,如(1,7),(3,4,8)等等。
这些子序列中和最大为18,为子序列(1,3,5,9)的和。
你的任务,就是对于给定的序列,求出最大上升子序列和。
注意,最长的上升子序列的和不一定是最大的,比如序列(100,1,2,3)的最大上升子序列和为100,而最长上升子序列为(1,2,3)。
样例输入:7
1 7 3 5 9 4 8
样例输出:18
思路:
第一步,确定状态表示:用x[i]表示以a[i]为结尾的最长上升子序列合
第二步,考虑状态计算:考虑状态计算:考虑当前数a[i]作为x[]数组的结尾,然后遍历i之前的数,会出现两种情况:
①:a[j]>=a[i](无法放入)
②:a[j]<a[i](可以放入)
这样x[i]代表的就是以a[i]为结尾的最长上升子序列合。
然后可以得到转移方程
x
[
i
]
=
m
a
x
(
x
[
i
]
,
x
[
j
]
+
a
[
i
]
(
1
<
=
j
<
i
,
a
[
i
]
>
a
[
j
]
)
)
x[i]=max(x[i],{x[j]+a[i]}(1<=j<i ,a[i]>a[j]))
x[i]=max(x[i],x[j]+a[i](1<=j<i,a[i]>a[j]))
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;
int x[1010],a[1010];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
x[i]=a[i];
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(a[i]>a[j]){
x[i]=max(x[i],x[j]+a[i]);
}
}
}
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,x[i]);
printf("%d\n",ans);
return 0;
}
AcWing 1017 怪盗基德的滑翔翼
题干:
怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。
而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。
有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。
不得已,怪盗基德只能操作受损的滑翔翼逃脱。
假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。
初始时,怪盗基德可以在任何一幢建筑的顶端。
他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。
因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。
他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。
请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?
输入格式
输入数据第一行是一个整数K,代表有K组测试数据。
每组测试数据包含两行:第一行是一个整数N,代表有N幢建筑。第二行包含N个不同的整数,每一个对应一幢建筑的高度h,按照建筑的排列顺序给出。
输出格式
对于每一组测试数据,输出一行,包含一个整数,代表怪盗基德最多可以经过的建筑数量。
数据范围
1≤K≤100,
1≤N≤100,
0<h<10000
输入样例:
3
8
300 207 155 299 298 170 158 65
8
65 158 170 298 299 155 207 300
10
2 1 3 4 5 6 7 8 9 10
输出样例:
6
6
9
思路:
虽然题干很长,但总的来说就是求最长下降子序列,但因为可以选择方向,从右向左的下降就是从左向右的上升,所以就转变成了求最长下降子序列和最长上升子序列的最大值。
#include<iostream>
#include <cmath>
#include <algorithm>
#include <cstdio>
using namespace std;
int a[1010],x[1010],y[1010];
int main()
{
int n,mx=0,t;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
mx=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
x[i]=y[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(a[i]>a[j])
x[i]=max(x[i],x[j]+1);
if(a[i]<a[j])
y[i]=max(y[i],y[j]+1);
}
}
for(int i=1;i<=n;i++){
//printf("%d %d\n",x[i],y[i]);
mx=max(mx,x[i]);
mx=max(mx,y[i]);
}
printf("%d\n",mx);
}
return 0;
}
AcWing 1014. 登山
题干:
五一到了,ACM队组织大家去登山观光,队员们发现山上一个有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。
同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。
队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?
输入格式
第一行包含整数N,表示景点数量。
第二行包含N个整数,表示每个景点的海拔。
输出格式
输出一个整数,表示最多能浏览的景点数。
数据范围
2≤N≤1000
输入样例:
8
186 186 150 200 160 130 197 220
输出样例:
4
思路:
总结一下题意,就是要求一个先上升后下降序列;换言之就是找到一个点,这个点左边是从左到右上升的,点右边从左到右下降。
第一步,确定状态表示:用x[i]表示以a[i]为结尾的最长上升子序列和;用y[i]表示以a[i]为起点的最长下降子序列和 ;然后x[i]+y[i]-1(因为a[i]多加了一次)
第二步,考虑状态计算:以a[i]为起点的最长下降子序列可以看作是将原数组逆置之后,以a[i]为结尾的最长上升子序列。然后就是正常的求两次LIS。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
int a[1100],x[1100],y[1100];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
x[i]=y[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(a[i]>a[j])
x[i]=max(x[i],x[j]+1);
}
}
for(int i=n;i>=1;i--){
for(int j=n;j>i;j--){
if(a[i]>a[j])
y[i]=max(y[i],y[j]+1);
}
}
int ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,x[i]+y[i]-1);
}
printf("%d\n",ans);
return 0;
}
AcWing 482. 合唱队形
题干:
N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。
合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为
T
1
,
T
2
,
…
,
T
K
T_1,T_2,…,T_K
T1,T2,…,TK, 则他们的身高满足
T
1
<
…
<
T
i
>
T
i
+
1
>
…
>
T
K
(
1
≤
i
≤
K
)
T_1<…<T_i>T_{i+1}>…>T_K(1≤i≤K)
T1<…<Ti>Ti+1>…>TK(1≤i≤K)。
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入格式
输入的第一行是一个整数N,表示同学的总数。
第二行有n个整数,用空格分隔,第i个整数Ti是第i位同学的身高(厘米)。
输出格式
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
数据范围
2≤N≤100
130≤Ti≤230
输入样例:
8
186 186 150 200 160 130 197 220
输出样例:
4
思路:
继续总结下题意…,求最少被移出的换言之就是求最多被保留的。
然后就和AcWing 1014. 登山一毛一样了其实多了个减法
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int a[110],x[110],y[110];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
x[i]=y[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(a[i]>a[j])
x[i]=max(x[i],x[j]+1);
}
}
for(int i=n;i>=1;i--){
for(int j=n;j>i;j--){
if(a[i]>a[j])
y[i]=max(y[i],y[j]+1);
}
}
int ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,x[i]+y[i]-1);
}
printf("%d\n",n-ans);
return 0;
}
AcWing 1012. 友好城市
题干:
Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。
北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。
每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。
编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。
输入格式
第1行,一个整数N,表示城市数。
第2行到第n+1行,每行两个整数,中间用1个空格隔开,分别表示南岸和北岸的一对友好城市的坐标。
输出格式
仅一行,输出一个整数,表示政府所能批准的最多申请数。
数据范围
1≤N≤5000
0≤xi≤10000
输入样例:
7
22 4
2 6
10 3
15 12
9 8
17 17
4 2
输出样例:
4
思路:
还是整理下题干,给定两个友好城市的坐标(南岸和北岸之间的不相互影响),然后这两个友好城市可以建立航线,航线之间不能相互交叉,样例如下图:
让我这么看你很爽吗.jpg
(其实是有多种选择的)
既然要求不出现交叉,我们可以先把一边排序(例:先把北岸排序),然后另一边成严格单调上升,因为如果出现下降就说明出现了交叉(例如:10-9,11-8)
所以还是最长上升子序列问题。
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef pair<int,int> PII;
PII q[5010];
int x[5010];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&q[i].first,&q[i].second);
x[i]=1;
}
sort(q+1,q+1+n);
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(q[i].second>q[j].second)
x[i]=max(x[i],x[j]+1);
}
}
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,x[i]);
printf("%d\n",ans);
return 0;
}
AcWing 1010. 拦截导弹(dilworth定理 或 贪心)
题干:
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。
但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
某天,雷达捕捉到敌国的导弹来袭。
由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
共一行,输入导弹依次飞来的高度。
输出格式
第一行包含一个整数,表示最多能拦截的导弹数。
第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。
输入样例:
389 207 155 300 299 170 158 65
输出样例:
6
2
思路:
做法一:首先分析题干,最多能拦截的导弹数就是个最长不上升子序列(因为高度可以相等)。
最少要配备的系统数,就是对所有数按不上升子序列进行划分,根据dilworth定理:对于任意有限偏序集,其最大反链中元素的数目必等于最小链划分中链的数目,此定理的对偶形式亦真。说白了,是不下降子序列的划分数,等于最长上升子序列的长度
偏序集:具有部分排序关系的集合(不一定单调)。
所以我们只需要求一个最长不上升和一个最长上升就行。
做法二:既然我们要求最少配备系统数,也就是每个系统尽可能拦截多个导弹,即当前导弹尽量找最小的满足条件的系统去拦截。
我们创建一个数组,x[i]表示第i套系统当前的拦截高度,不满足条件则新建一个数组。
按照上述规则建立的系统满足低的靠前,高的靠后。
//贪心思路
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int x[10000],y[10000],a[10000];
int main(){
int n,sum=1;
while(scanf("%d",&n)!=EOF){
x[sum]=1;
a[sum++]=n;
//printf("%d ",n);
}
for(int i=1;i<sum;i++){
for(int j=1;j<i;j++){
if(a[i]<=a[j])
x[i]=max(x[i],x[j]+1);
}
}
int ans=0;
for(int i=1;i<sum;i++){
ans=max(ans,x[i]);
}
int now=1,tem=0;
while(now<sum){
int sta=0;
while(sta<tem&&y[sta]<a[now])
sta++;
if(sta==tem)
y[tem++]=a[now];
else
y[sta]=a[now];
now++;
}
printf("%d\n%d\n",ans,tem);
return 0;
}
//dilworth定理
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int x[10000],y[10000],a[10000];
int main(){
int n,sum=1;
while(scanf("%d",&n)!=EOF){
x[sum]=y[sum]=1;
a[sum++]=n;
//printf("%d ",n);
}
for(int i=1;i<sum;i++){
for(int j=1;j<i;j++){
if(a[i]<=a[j])
x[i]=max(x[i],x[j]+1);
}
}
for(int i=1;i<sum;i++){
for(int j=1;j<i;j++){
if(a[i]>a[j])
y[i]=max(y[i],y[j]+1);
}
}
int ans=0,ans1=0;
for(int i=1;i<sum;i++){
ans=max(ans,x[i]);
ans1=max(ans1,y[i]);
}
printf("%d\n%d\n",ans,ans1);
return 0;
}
AcWing 187. 导弹防御系统 (贪心+dfs/迭代加深)
题干:
为了对抗附近恶意国家的威胁,R国更新了他们的导弹防御系统。
一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。
例如,一套系统先后拦截了高度为3和高度为4的两发导弹,那么接下来该系统就只能拦截高度大于4的导弹。
给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。
输入格式
输入包含多组测试用例。
对于每个测试用例,第一行包含整数n,表示来袭导弹数量。
第二行包含n个不同的整数,表示每个导弹的高度。
当输入测试用例n=0时,表示输入终止,且该用例无需处理。
输出格式
对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。
数据范围
1≤n≤50
输入样例:
5
3 5 2 4 1
0
输出样例:
2
样例解释
对于给出样例,最少需要两套防御系统。
一套击落高度为3,4的导弹,另一套击落高度为5,2,1的导弹
思路:
1、dfs:既然我们不知道上升和下降系统的个数,不如对每个导弹进行枚举,利用dfs的特性使两种可能都贪心一边。记录全局最小值,dfs参数为当前枚举的导弹now,上升数组长度up,下降数组dw。
2、迭代加深:
//dfs版
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int a[110],x[110],y[110],n,ans;
void dfs(int now,int up,int dw){
if(up+dw>=ans)
return;
if(now==n){
ans=up+dw;
return;
}
int k=0;
while(k<up&&x[k]>=a[now])
k++;
if(k<up){
int t=x[k];
x[k]=a[now];
dfs(now+1,up,dw);
x[k]=t;
}
else{
x[k++]=a[now];
dfs(now+1,up+1,dw);
}
k=0;
while(k<dw&&y[k]<=a[now])
k++;
if(k<dw){
int t=y[k];
y[k]=a[now];
dfs(now+1,up,dw);
y[k]=t;
}
else{
y[k++]=a[now];
dfs(now+1,up,dw+1);
}
}
int main(){
while(scanf("%d",&n)&&n){
ans=n;
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
dfs(0,0,0);
printf("%d\n",ans);
}
return 0;
}
//迭代加深版(yxc大佬的)
#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;
}
作者:yxc
链接:https://www.acwing.com/solution/AcWing/content/4258/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。