最长上升子序列及拓展

目录

最长上升子序列

怪盗基德的滑翔翼

登山

合唱队形

友好城市

拦截导弹

导弹防御系统 

最长公共上升子序列 

朴素版(容易TLE) 

优化


最长上升子序列

【题目描述】

一个数的序列bibi,当b1<b2<...<bSb1<b2<...<bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1,a2,...,aN)(a1,a2,...,aN),我们可以得到一些上升的子序列(ai1,ai2,...,aiK)(ai1,ai2,...,aiK),这里1≤i1<i2<...<iK≤N1≤i1<i2<...<iK≤N。比如,对于序列(1,7,3,5,9,4,8),有它的一些上升子序列,如(1,7),(3,4,8)等等。这些子序列中最长的长度是4,比如子序列(1,3,5,8)。

你的任务,就是对于给定的序列,求出最长上升子序列的长度。

【输入】

输入的第一行是序列的长度N(1≤N≤1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。

【输出】

最长上升子序列的长度。

【输入样例】

7
1 7 3 5 9 4 8

【输出样例】

4
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int f[N],a[N],g[N];
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    cin>>a[i];
    for(int i=0;i<n;i++)
    {
        f[i]=1;
        for(int j=0;j<i;j++)
        if(a[j]<a[i])
        {
            f[i]=max(f[i],f[j]+1);
            // if(f[i]<f[j]+1)
            // {
            //     f[i]=f[j]+1;
            //     g[i]=j;
                
            // }
        }
    }

    // int k = 1;
    // for (int i = 1; i <= n; i ++)
    //     if (f[i] > f[k]) k = i;
    // cout << f[k] << endl;

    // for (int i = 0, len = f[k]; i < len; i ++)
    // {
    //     cout << a[k] << ' ';
    //     k = g[k];
    // }
    int res=0;
    for(int i=0;i<n;i++)
    res=max(res,f[i]);
    cout<<res;
    return 0;
}


怪盗基德的滑翔翼

【题目描述】

怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。

有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。不得已,怪盗基德只能操作受损的滑翔翼逃脱。

假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。初始时,怪盗基德可以在任何一幢建筑的顶端。他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?

【输入】

输入数据第一行是一个整数K(K<100) ,代表有KK组测试数据。

每组测试数据包含两行:第一行是一个整数N(N<100)N(N<100),代表有N幢建筑。第二行包含NN个不同的整数,每一个对应一幢建筑的高度h(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<bits/stdc++.h>
using namespace std;
const int N=1010,INF=0x3f3f3f3f;
int a[N],f[N],g[N];
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
      int n;
    cin>>n;
    memset(f,0,sizeof f);
    memset(g,0,sizeof g);
    for(int i=1;i<=n;i++)
    cin>>a[i];
    for(int i=1;i<=n;i++)
    {
        f[i]=1;
        for(int j=1;j<=i;j++)
        if(a[j]<a[i])
        f[i]=max(f[i],f[j]+1);
    }
     for(int i=n;i>=1;i--)
    {
        g[i]=1;
        for(int j=n;j>=i;j--)
        if(a[j]<a[i])
        g[i]=max(g[i],g[j]+1);
    }
    for(int i=1;i<=n;i++)
    f[i]=max(f[i],g[i]);
    int res=-INF;
    for(int i=1;i<=n;i++)
    res=max(res,f[i]);
    cout<<res<<endl;  
    }
    
}

登山

【题目描述】

五一到了,ACM队组织大家去登山观光,队员们发现山上一共有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?

【输入】

第一行:N (2 <= N <= 1000) 景点数;

第二行:N个整数,每个景点的海拔。

【输出】

最多能浏览的景点数。

【输入样例】

8
186 186 150 200 160 130 197 220

【输出样例】

4


#include<bits/stdc++.h>
using namespace std;
const int N=1010,INF=0x3f3f3f3f;
int a[N],f[N],g[N];
int main()
{
  
      int n;
    cin>>n;

    for(int i=1;i<=n;i++)
    cin>>a[i];
    for(int i=1;i<=n;i++)
    {
        f[i]=1;
        for(int j=1;j<=i;j++)
        if(a[j]<a[i])
        f[i]=max(f[i],f[j]+1);
    }
     for(int i=n;i>=1;i--)
    {
        g[i]=1;
        for(int j=n;j>=i;j--)
        if(a[j]<a[i])
        g[i]=max(g[i],g[j]+1);
    }

    int res=-INF;
    for(int i=1;i<=n;i++)
    res=max(res,f[i]+g[i]-1);
    cout<<res<<endl;  
    
}

合唱队形

【题目描述】

NN位同学站成一排,音乐老师要请其中的(N−K)位同学出列,使得剩下的KK位同学排成合唱队形。

合唱队形是指这样的一种队形:设KK位同学从左到右依次编号为1,2,…,K,他们的身高分别为T1,T2,…,TK,则他们的身高满足T1<T2<…<Ti,Ti>Ti+1>…>TK(1≤i≤K)。

你的任务是,已知所有NN位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

【输入】

输入的第一行是一个整数N(2≤N≤100)N,表示同学的总数。第二行有nn个整数,用空格分隔,第ii个整数Ti(130≤Ti≤230)是第i位同学的身高(厘米)。

【输出】

输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。

【输入样例】

8
186 186 150 200 160 130 197 220

【输出样例】

4

和登山一样,需要改一下结果,要取出人 


#include<bits/stdc++.h>
using namespace std;
const int N=1010,INF=0x3f3f3f3f;
int a[N],f[N],g[N];
int main()
{
  
      int n;
    cin>>n;

    for(int i=1;i<=n;i++)
    cin>>a[i];
    for(int i=1;i<=n;i++)
    {
        f[i]=1;
        for(int j=1;j<=i;j++)
        if(a[j]<a[i])
        f[i]=max(f[i],f[j]+1);
    }
     for(int i=n;i>=1;i--)
    {
        g[i]=1;
        for(int j=n;j>=i;j--)
        if(a[j]<a[i])
        g[i]=max(g[i],g[j]+1);
    }

    int res=-INF;
    for(int i=1;i<=n;i++)
    res=max(res,f[i]+g[i]-1);
    cout<<n-res<<endl;  
    
}

友好城市

【题目描述】

Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。

每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。

【输入】

第1行,一个整数N(1≤N≤5000),表示城市数。

第2行到第n+1行,每行两个整数,中间用1个空格隔开,分别表示南岸和北岸的一对友好城市的坐标。(0<≤xi≤10000)

【输出】

仅一行,输出一个整数,表示政府所能批准的最多申请数。

【输入样例】

7
22 4
2 6
10 3
15 12
9 8
17 17
4 2

【输出样例】

4

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
const int N=5010;

typedef pair<int,int> PII;
int f[N];
PII p[N];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>p[i].x>>p[i].y;
	}
	sort(p+1,p+n+1);
	for(int i=1;i<=n;i++)
	{
		f[i]=1;
		for(int j=1;j<i;j++)
		{
			if(p[i].y>p[j].y)f[i]=max(f[i],f[j]+1);
		}	
	}
	int res=0;
	for(int i=1;i<=n;i++)
	res=max(f[i],res);
	cout<<res<<endl;
	
 } 

拦截导弹

【题目描述】

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

【输入】

输入导弹依次飞来的高度。

【输出】

第一行:最多能拦截的导弹数;

第二行:要拦截所有导弹最少要配备的系统数。

【输入样例】

389 207 155 300 299 170 158 65

【输出样例】

6
2
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int f[N],a[N];
bool vis[N];
int main()
{
	int n=0;
	int x;
	while(cin>>x)a[++n]=x;

	int res=0;
	
	//求最长不上升子序列
	 
	for(int i=n;i>=1;i--)
	{
		f[i]=1;
		for(int j=n;j>i;j--)
		if(a[i]>=a[j])
		f[i]=max(f[i],f[j]+1);
		res=max(res,f[i]);
	}
	cout<<res<<endl;
	
    //求数组最少能被几个最长下降子序列全部覆盖


	int cnt=0,h=0,ans=0;  //cnt是统计已经拦截的导弹
                          //h每次拦截后的高度                                    
                          //ans最少导弹拦截系统
	while(cnt<n)
	{
		ans++;
		h=30000;
		for(int i=1;i<=n;i++)
		{
			if(h>=a[i]&&!vis[i])
			{
				h=a[i];          //更新高度
				vis[i]=1;        //被访问过
				cnt++;
			}
		}
	}
	cout<<ans<<endl;
	 
}

导弹防御系统 

导弹防御系统【剪枝:迭代加深/全局最小值】

为了对抗附近恶意国家的威胁,RR 国更新了他们的导弹防御系统。

一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。

例如,一套系统先后拦截了高度为 3 和高度为 4 的两发导弹,那么接下来该系统就只能拦截高度大于 44 的导弹。

给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。

输入格式

输入包含多组测试用例。

对于每个测试用例,第一行包含整数 nn,表示来袭导弹数量。

第二行包含 n 个不同的整数,表示每个导弹的高度。

当输入测试用例 n=0时,表示输入终止,且该用例无需处理。

输出格式

对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。

数据范围

1≤n≤50

输入样例:

5
3 5 2 4 1
0 

输出样例:

2

样例解释

对于给出样例,最少需要两套防御系统。

一套击落高度为 3,4 的导弹,另一套击落高度为 5,2,1 的导弹。

#include <iostream>
using namespace std;

// 这题是拦截拦截第二问的加强版
// 拦截导弹第二问是只考虑下降序列的方案数,因此直接用贪心搜出最优解即可(证明在那一题的笔记里)
// 这一题,确实要考虑下降和上升两种序列的方案数
// 因此只能用dfs进行爆搜两种方案的搭配,但无论是上升还是下降方案,依然采用上一题的贪心思路

const int N = 55;
int n;
int a[N];
int up[N], down[N];
int res;

//三个参数分别是考虑前u个导弹
//已经采用了上升系统个数sum_up
//和下降系统个数sum_down
void dfs(int u, int sum_up, int sum_down) {
    //如果已经超过了最优解答案,那么直接剪枝
    if (sum_up + sum_down >= res) return;
    //没有超过最优解答案,且把所有导弹都考虑到了
    //那他就是当前最优解了
    if (u == n) {
        res = sum_up + sum_down;
        return;
    }
    //情况一:考虑用上升拦截系统来拦截第u个导弹
    // 上升拦截系统的贪心思路是:
    //     如果当前已有的上升拦截系统的高度都大于第u个导弹高度,则重新开一套系统
    //     否则,则由当前低于第u个导弹最高拦截系统来负责拦截
    int k = 0;
    while (k < sum_up && up[k] >= a[u]) ++k;
    //找到了有这么个拦截系统
    int t = up[k]; //t用于dfs回溯的时候恢复现场
    up[k] = a[u];
    if (k >= sum_up) dfs(u + 1, sum_up + 1, sum_down);
    else dfs(u + 1, sum_up, sum_down);
    //恢复现场
    up[k] = t;

    //情况二:考虑用下降拦截系统来拦截第u个导弹
    // 下降拦截系统的贪心思路是:
    //     如果当前已有的下降拦截系统的高度都小于第u个导弹高度,则重新开一套系统
    //     否则,则由当前大于第u个导弹最低拦截系统来负责拦截
    k = 0;
    while (k < sum_down && down[k] <= a[u]) ++k;
    t = down[k]; //t用于dfs回溯的时候恢复现场
    down[k] = a[u];
    if (k >= sum_down) dfs(u + 1, sum_up, sum_down + 1);
    else dfs(u + 1, sum_up, sum_down);
    //恢复现场
    down[k] = t;
}

int main() {
    while (cin >> n, n) {
        for (int i = 0; i < n; ++i) cin >> a[i];
        //最差情况是n个导弹分别用n个系统拦截
        //因此可以设置res初始为n来设立哨兵
        res = n;
        dfs(0, 0, 0);
        cout << res << endl;
    }
    return 0;
}

最长公共上升子序列 

熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。

小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。

小沐沐说,对于两个数列 A 和 B,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。

奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。

不过,只要告诉奶牛它的长度就可以了。

数列 A 和 B 的长度均不超过 3000。

输入格式

第一行包含一个整数 N,表示数列 A,B 的长度。

第二行包含 N 个整数,表示数列 A。

第三行包含 N 个整数,表示数列 B。

输出格式

输出一个整数,表示最长公共上升子序列的长度。

数据范围

1≤N≤3000,序列中的数字均不超过 231−1。

输入样例:

4
2 2 1 3
2 1 2 3

输出样例:

2

 

朴素版(容易TLE) 


#include<bits/stdc++.h>
using namespace std;
const int N=3010;
int a[N],b[N],f[N][N];
int main()
{
  int n,ans=0;
  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<=n;j++)
  {
      f[i][j]=f[i-1][j];//所有不包含a[i]的最长公共上升子序列
      if(a[i]==b[j])//所有包含a[i]的最长公共上升子序列
      {
          f[i][j]=max(f[i][j],1);//当空的时候是自己,也就是1
          for(int k=1;k<j;k++)//求最长公共上升子序列
            if(b[k]<b[j])
                f[i][j]=max(f[i][j],f[i][k]+1);
      }
  }
  //求最大值,第一个序列的前n个字母,与b的匹配最大
  for(int i=1;i<=n;i++) ans=max(ans,f[n][i]);
 printf("%d\n",ans);
    return 0;
}

优化


#include<bits/stdc++.h>
using namespace std;
const int N=3010;
int a[N],b[N],f[N][N];
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 maxv=1;//由于在k中重复求前j个最大值,所有用一个maxv记录前j个的最长上升子序列的最大值
      for(int j=1;j<=n;j++)
    {
         f[i][j]=f[i-1][j];//所有不包含a[i]的最长公共上升子序列
      if(a[i]==b[j]) f[i][j]=max(f[i][j],maxv);//所有包含a[i]的最长公共上升子序列,更新最大值
      if(b[j]<a[i])  maxv=max(maxf,f[i][j]+1);//因为a[i]==b[j]的时候更新最大值,所以可以把b[j]换成a[i],用来与新的b[j]比较
    }
  }
  //最后更新一遍最大值
  int ans=0;
  for(int i=1;i<=n;i++) ans=max(ans,f[n][i]);
 printf("%d\n",ans);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值