**********************************************************************************
晒题:
题目1131:合唱队形一直在自学王道的书,昨天开始进入dp阶段,先做了拦截导弹,但死活通不过,给的测试案例是可以过的,在OJ上就WA,十分郁闷。今天做这道题,写最大上升子序列的部分时幡然醒悟,昨天的算法错了。。。。昨天的LIS部分我是这么写的:
for(i=2;i<=k;i++){
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~
****************************************************************************************************
坚持,而不是打鸡血