九度1131:合唱队形

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了九度1131:合唱队形相关的知识,希望对你有一定的参考价值。

**********************************************************************************

晒题:

 

题目1131:合唱队形

时间限制:1 秒

内存限制:32 兆

特殊判题:否

提交:5059

解决:1601

题目描述:

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

输入:

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

输出:

可能包括多组测试数据,对于每组数据,
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。

样例输入:
8
186 186 150 200 160 130 197 220
样例输出:
4
来源:
2008年北京大学方正实验室计算机研究生机试真题

 

一直在自学王道的书,昨天开始进入dp阶段,先做了拦截导弹,但死活通不过,给的测试案例是可以过的,在OJ上就WA,十分郁闷。今天做这道题,写最大上升子序列的部分时幡然醒悟,昨天的算法错了。。。。昨天的LIS部分我是这么写的:

 

for(i=2;i<=k;i++){       ///对于以a[k]结尾的子序列
            for(j=i-1;j>=1;j--){
                if(a[i]<=a[j]&&f[i]<=f[j]+1){
                    f[i]=f[j]+1;
                    break;
                }
            }
            if(j<1)
                f[i]=1;

}

  

即对于每一个数列中的元素,我没有从前面开始遍历,而是从这个元素开始逐个往前找,找到一个满足条件的直接加1就跳出循环了,这样是不对的,有可能时结果变小了。。正确的算法应该是从第一个元素开始遍历,一会我会把相应部分加粗。坑爹的是这么写的话本地案例居然可以过。。。

再说这道题,我又被scanf函数给坑了,本来已经写好了一版代码,如下:

 

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int t[101];

int rev_t[101];
void revv(int a[],int m)
{
    for(int i=0;i<m;i++){
        rev_t[m-i-1]=a[i];
    }
}
void find_long_up(int a[],int num,int b[])
{
    int i,j;
    for(i=1;i<num;i++){
        for(j=0;j<i;j++){
            if(a[i]>a[j]&&b[i]<b[j]+1)
                b[i]=b[j]+1;
        }
    }
}
int main()
{
    int n,i;
    while(scanf("%d",&n)!=EOF){
        int max=1;
        int f_long_up[101];
        int f_long_up_rev[101];
        for(i=0;i<n;i++){
            f_long_up[i]=1;
            f_long_up_rev[i]=1;
        }
        for(i=0;i<n;i++){
            scanf("%d",&t[i]);
        }
        find_long_up(t,n,f_long_up);
        revv(t,n);
        find_long_up(rev_t,n,f_long_up_rev);
        for(i=0;i<n;i++){
            if(f_long_up[i]+f_long_up_rev[n-1-i]>max)
                max=f_long_up[i]+f_long_up_rev[n-1-i];
        }
        printf("%d\n",n-max+1);
    }
    return 0;
}

  


本来while循环条件那里,我没有写EOF,结果就一直超时。。。后来才发现如果不加的话while会变成死循环的,ctrl+z都退不出来。。可我刚开始不知道,百思不得其解,觉的复杂度和边界条件都没问题,于是写了第二版代码:

 

#include <cstdio>
int main()
{
    int n,i,j;
    while(scanf("%d",&n)!=EOF){
        int t[n];
        int f1[n],f2[n];

        for(i=0;i<n;i++){
            scanf("%d",&t[i]);
            f1[i]=1;f2[i]=1;
        }
        for(i=1;i<n;i++){
            for(j=0;j<i;j++){
                if(t[i]>t[j]&&f1[i]<f1[j]+1)
                    f1[i]=f1[j]+1;
            }
            
        }
        for(i=n-2;i>-1;i--){
            for(j=n-1;j>i;j--){
                if(t[i]>t[j]&&f2[i]<f2[j]+1)
                    f2[i]=f2[j]+1;
            }
        }

        int mx=1;
        for(i=0;i<n;i++){
            if(f1[i]+f2[i]-1>mx)
                mx=f1[i]+f2[i]-1;
        }
        printf("%d\n",n-mx);
    }
}

  

 


刚写出来也没加EOF,于是再次超时。。。我就开始怀疑了,可能是哪里进入死循环了。。拿ctrl+z试了一下果然没退出来......我就知道了.......

 

上面两版代码都能用,第一种用了子函数,第二种直接全在主函数中处理的。算法很明确,就是正反各用一次LIS,但有很多细节需要注意,比如代码2的LIS部分,正反两个循环,就必须分开,不能都合在第一个里,因为计算过程中,当前待计算的元素之前的所有元素都必须已经被计算过了,如果都合在

for(i=1;i<n;i++)

这个循环里,那么计算f2[i]时,后面的f2[j]其实都是没有被算过的,结果必然不可能总是准的(有一些情况可能碰上,比如本地案例......)。

 

这是其一,其二,最终输出结果时,由于正反两次序列中间的元素被算了两次,所以要减1.

再说说第一版代码,由于多组数据,每次测试f_long_up和f_long_up_rev这两个数组肯定要更新的,开始时我用memset。。结果果断被坑。。。。大家注意,这个函数很危险,它是以字节为单位来初始化内存的,第三个参数必须是sizeof(类型)*个数,才能不会错。这还不算,它一般被用来初始化为全0.。如果初始化值换成个别的,比如1,那就惨了,因为因为他会把数组元素都变成0000 0001 0000 0001 0000 0001 0000 0001........因为int是4个字节的......这是一个很大的数,程序自然不正常了。结论:他很危险。还是用for循环或在声明时初始化吧。

还有一点,是从别人学到的。有人说用cin和cout代替printf 和scanf,会超时。。我试了一下,果然这样。尽管“while(cin>>n)”这么写不加EOF也可以ctrl+z退出(正因为此,我误以为scanf也可以这样。。),但就是超时。看了知乎明白了。

总之,今天一天终于掌握了LIS~

****************************************************************************************************

坚持,而不是打鸡血

以上是关于九度1131:合唱队形的主要内容,如果未能解决你的问题,请参考以下文章

题目1131:合唱队形

1131.合唱队形

Pascal编程题(合唱队形)

NOIP200407合唱队形+最长上升子序列O(n^2)详解

codevs1058 合唱队形==洛谷P1091 合唱队形

动态规划合唱队形 luogu-