**********************************************************************************
晒题:
题目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
一直在自学王道的书,昨天开始进入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~
****************************************************************************************************
坚持,而不是打鸡血