一、最长上升子序列+输出路径

最长上升子序列

题目描述

给定一个整数序列A1A2A3….An。求它的一个递增子序列,使子序列的元素个数尽量多,元素不一定要求连续。

输入

第1行:1个整数n(1<=n<=5000),表示序列中元素的个数.

第2行-n+1行:每行1个整数x(-1000<=x<=1000),第i+1行表示序列中的第i个元素。

输出

第1行:1个整数k,表示最长上升子序列的长度。

第2行:k个用单个空格分开的整数,表示找到了最长上升子序列。如果有多个长度等于k的子序列,则输出最靠前的1个。

样例输入

8
1
3
2
4
3
5
4
6

样例输出

5
1 3 4 5 6


第一问很简单,用一个数组f[i]来表示前i个数之中的最长上升子序列,用一个循环来枚举所有的数,另一个循环来枚举前面的数以便更新现在的数值。

如何处理第二问呢?

其实再用一个数组g[i]来存f[i]的前驱就可以了,输出的时候用一个循环找回去。

#include<cstdio>
int a[5005][3];//把f[i]和g[i]合并为一个数组
int main()
{
    int n,i,j,l,k;
    scanf("%d",&n);
    for(i=1;i<=n;i++){
        scanf("%d",&a[i][0]);
        a[i][1]=1;a[i][2]=0;
    }
    for(i=n-1;i>=1;i--){
        l=0;k=0;
        for(j=i+1;j<=n;j++){
            if(a[j][0]>a[i][0]&&a[j][1]>l){
                l=a[j][1];
                k=j;
            }
            if(l>0){
                a[i][1]=l+1;
                a[i][2]=k;
            }
        }
    }
    k=1;
    for(j=1;j<=n;j++)
        if(a[j][1]>a[k][1]) k=j;
    printf("%d\n",a[k][1]);
    printf("%d",a[k][0]);
    k=a[k][2];
    while(k!=0){
        printf(" %d",a[k][0]);
        k=a[k][2];
    }
}

二、DP+贪心

拦截导弹

题目描述

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

输入

第1行:依次输入若干个导弹的高度H(1≤H≤30000),导弹的个数N≤5000

输出

第1行:一个整数,表示单枚炮弹能拦截多少导弹 第2行:一个整数,表示拦截所有导弹最少要配备多少套这种导弹拦截系统

样例输入

 (如果复制到控制台无换行,可以先粘贴到文本编辑器,再复制)

389 207 155 300 299 170 158 65

样例输出

6
2

提示

第2问最直观的算法是贪心,但是反例也容易找到,如:
6 5 1 7 3 2
如果第一次打6 5 3 2,显然还要打两次,而最好的方案是6 5 1/7 3 2。


做法跟上一道题差不多,但是贪心的策略不同,这一道题是先用一套系统,如果能够打中就打,不行的话就换一套系统继续打,而不是每次都打最长的。

#include<cstdio>
int a[5005],b[5005],h[5005];
int main()
{
    int i,j,n,m,x,s;
    i=1;n=0;m=0;
    while(scanf("%d",&a[i])==1){
        s=0;
        for(j=1;j<i;j++)
            if(a[j]>=a[i]&&b[j]>s)
                s=b[j];
        b[i]=s+1;
        if(b[i]>m) m=b[i];
        x=0;
        for(j=1;j<=n;j++)
            if(h[j]>=a[i]){
                if(x==0) x=j;
                else if(h[j]<h[x]) x=j;
            }
        if(x==0){n++;x=n;}
        h[x]=a[i];
        i++;    
    }
    printf("%d\n%d",m,n);
}


三、最长上升子序列*2

合唱队形

题目描述

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。 合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足存在i(1≤ i ≤K)使得Ti <T2< ......< Ti-1 < Ti >Ti+1 >......>TK。 你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入

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

输出

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

样例输入

 (如果复制到控制台无换行,可以先粘贴到文本编辑器,再复制)

8
186 186 150 200 160 130 197 220

样例输出

4

提示

数据规模 对于50%的数据,保证有n≤20; 对于全部的数据,保证有n≤100。


做法差不多,做两次最长上升子序列就可以了。(直接丢代码)

#include<cstdio>
#include<algorithm>
using namespace std;
int a[105],b[105],c[105];
int main()
{
    int n,i,j,s=0;
    scanf("%d",&n);
    for(i=1;i<=n;i++){
        scanf("%d",&a[i]);
        b[i]=1;c[i]=1;
    }
    for(i=2;i<=n;i++){
        for(j=i-1;j>=1;j--){
            if(a[j]<a[i])
                b[i]=max(b[i],b[j]+1);
        }
    }
    for(i=n-1;i>=1;i--){
        for(j=i+1;j<=n;j++){
            if(a[j]<a[i])
                c[i]=max(c[i],c[j]+1);
        }
    }
    for(i=1;i<=n;i++){
        //printf("%d ",b[i]+c[i]);
        if(b[i]+c[i]>s)
            s=b[i]+c[i];
    }
    printf("%d",n-s+1);
}

四、排序+DP

渡轮问题

题目描述

Palmia 河在某国从东向西流过,并把该国分为南北两个部分。河的两岸各有 N 座城市,且北岸的每一个城市都与南岸的某个城市是友好城市,而且友好关系是一一对应的。现在要求在两个友好城市之间建立一条航线,但由于天气的原因,所有航线都不能相交,因此,就不可能给所有的友好城市建立航线。 问题:当给出城市个数和友好关系的信息之后,选择一种修建航线的方案,能建最多的航线而不相交。

输入

第1行:2个空格分开的整数X, Y,X (10≤X≤6000)表示Palmia河岸的长度,Y(10≤Y≤ 100)表示河的宽度 第2行:1个整数N (1≤N≤5000),表示在河两岸分别有多少座城市 接下来N行:每行2个空格分开的非负整数C、D(C,D ≤X),表示河两岸的一对友好城市从河最西端算起的坐标(C表示北岸的城市,D表示南岸的城市),在同一岸上没有两座城市的坐标相同。

输出

第1行: 最多能建立航线的数量K 接下来K行:每行2个用1个空格分开的整数,按北岸城市的坐标从小到大的顺序输出建立航线的K对友好城市的坐标。若有多种方案,选择北岸城市(从后往前数起)编号大的那种方案

样例输入

 (如果复制到控制台无换行,可以先粘贴到文本编辑器,再复制)

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

样例输出

4
4 2
10 3
15 12
17 17

一个排序加上一个最长上升子序列,做法很简单。只不过要注意一下输出。

#include<cstdio>
struct node{
    int x,y;
}a[5005],t;
int b[5005][2];
int main()
{
    int x,y,n,i,j,k,l;
    scanf("%d%d%d",&x,&y,&n);
    for(i=1;i<=n;i++){
        scanf("%d%d",&a[i].x,&a[i].y);
        b[i][0]=1;b[i][1]=0;
    }
    for(i=1;i<n;i++)
        for(j=i+1;j<=n;j++)
            if(a[i].y>a[j].y){
                t=a[i];
                a[i]=a[j];
                a[j]=t;
            }
    for(i=n-1;i>=1;i--){
        l=0;k=0;
        for(j=i+1;j<=n;j++){
            if(a[j].x>a[i].x&&b[j][0]>l){
                l=b[j][0];
                k=j;
            }
            if(l>0){
                b[i][0]=l+1;
                b[i][1]=k;
            }
        }
    }
    k=1;
    for(i=1;i<=n;i++)
        if(b[i][0]>b[k][0]) k=i;
    printf("%d\n",b[k][0]);
    while(k!=0){
        printf("%d %d\n",a[k].x,a[k].y);
        k=b[k][1];
    }
}