动态规划合集

模板:

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]=maxx[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[i1][j]),(x[i][j1]),(x[i1][j1]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]=maxx[i][j],x[i1][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[i1][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[i1][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 1i1<i2<<iKN
比如,对于序列(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]=maxx[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 T1T2TK,  则他们的身高满足 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(1iK)。     
你的任务是,已知所有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
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值