• 九度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-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-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~

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

    坚持,而不是打鸡血

  • 相关阅读:
    对于JavaScript中this关键字的理解
    使用DOM对表格进行增删
    sql server 存储过程
    sql sever 基础 练习题
    sql sever 基础 建表
    第十章 嵌入式的Linux调试技术
    第九章 硬件抽象层 HAL
    第八章 蜂鸣器驱动
    LED:控制发光二极管
    第一个Linux驱动程序:统计单词个数
  • 原文地址:https://www.cnblogs.com/cjf1699-dut/p/7359706.html
Copyright © 2020-2023  润新知