最长不下降子序列 nlogn && 输出序列
最长不下降子序列实现:
利用序列的单调性。
对于任意一个单调序列,如 1 2 3 4 5(是单增的),若这时向序列尾部增添一个数 x,我们只会在意 x 和 5 的大小,若 x>5,增添成功,反之则失败。由于普通代码是从头开始比较,而 x 和 1,2,3,4 的大小比较是没有用处的,这种操作只会造成时间的浪费,所以效率极低。对于单调序列,只需要记录每个序列的最后一个数,每增添一个数 x,直接比较 x 和末尾数的大小。只有最后一个数才是有用的,它表示该序列的最大限度值。
实现方法就是新开一个数组 d,用它来记录每个序列的末尾元素,以求最长不下降为例,d[k] 表示长度为k的不下降子序列的最小末尾元素。
我们用 len 表示当前凑出的最长序列长度,也就是当前 d 中的最后那个位置。
这样就很 easy 了,每读入一个数 x,如果 x 大于等于 d[len],直接让 d[len+1]=x,然后 len++,相当于把 x 接到了最长的序列后面;
如果 x 小于 d[len],说明 x 不能接到最长的序列后面,那就找 d[1...len−1] 中末尾数小于等于 x 的的序列,然后把 x 接到它后面。举个例子,若当前 x==7,len==8:
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
2 |
3 |
4 |
7 |
7 |
10 |
12 |
29 |
d[1]⋯d[5] 均小于等于 x,若在 d[1] 后接 x,则 d[2] 应换成 x,但 d[2]==3,比 x 小,能接更多的数,用 7 去换 3 显然是不划算的,所以 x 不能接 d[1] 后。同理,d[2]⋯d[4] 均不能接x。由于 d[5]≤x 且 x<d[6],7 能比 10 接更多的数,所以选择在 d[5] 后接 x,用 x 替换 10。
根据这个操作过程,易知数组 d 一定是单调的序列,所以查找的时候可以用二分!二分效率是 logn 的,所以整个算法的效率就是 nlogn 的啦~
输出序列实现:
想了好久,认为 nlogn 做法也是可以输出序列的,这时候需要增加一个 c 数组 用来记录每个元素在最长序列中的位置,即 c[i] 表示 a[i] 被放到了序列的第几个位置。
输出时,从 数组 a 的尾部开始,逆序依次找出 c 为 len, len-1, len-2 … 3, 2, 1 的元素,并且找到一个就接着寻找 c[i]-1,直到找到 c[i] 为 1 的数。
举个例子:
a: | 13 | 7 | 9 | 16 | 38 | 24 | 37 | 18 | 44 | 19 | 21 | 22 | 63 | 15 |
c: | 1 | 1 | 2 | 3 | 4 | 4 | 5 | 4 | 6 | 5 | 6 | 7 | 8 | 3 |
len = 8;
我们从 15 开始倒着找 c 为 8 的元素,找到 63,接着找 c 为 7 的,找到 22,再找 c 为 6 的,找到 21,再找 c 为 5 …… 以此类推。
从而,我们得出的序列为 63,22,21,19,18,16,9,7
逆序输出来,就是 7,9,16,18,19,21,22,63
为什么这个方法是对的呢?倒序查找保证了两个条件:
- 如果 c 中有多个相同的数,后面的一定是最新更新的;
- 在保证一条件的前提下,倒序找,后面的数一定可以接到前面数的后面。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<stack> 6 #define int long long 7 #define maxn 1000+10 8 using namespace std; 9 inline int read() 10 { 11 int x=0; 12 bool f=1; 13 char c=getchar(); 14 for(; !isdigit(c); c=getchar()) if(c=='-') f=0; 15 for(; isdigit(c); c=getchar()) x=(x<<3)+(x<<1)+c-'0'; 16 if(f) return x; 17 return 0-x; 18 } 19 inline void write(int x) 20 { 21 if(x<0){putchar('-');x=-x;} 22 if(x>9)write(x/10); 23 putchar(x%10+'0'); 24 } 25 int n,len; 26 int a[maxn],last[maxn],site[maxn]; 27 stack<int> s; 28 signed main() 29 { 30 n=read(); 31 for(int i=1;i<=n;i++) a[i]=read(); 32 if(n==0) 33 { 34 write(0); 35 return 0; 36 } 37 len=1; 38 last[1]=a[1]; 39 site[1]=1; 40 for(int i=2;i<=n;i++) 41 { 42 if(a[i]>=last[len]) 43 { 44 last[++len]=a[i]; 45 site[i]=len; 46 } 47 else 48 { 49 int now=lower_bound(last+1,last+len+1,a[i])-last; 50 last[now]=a[i]; 51 site[i]=now; 52 } 53 } 54 printf("max="); 55 write(len); 56 printf("\n"); 57 for(int i=n,j=len;i>=1;i--) 58 { 59 if(site[i]==j) 60 { 61 s.push(a[i]); 62 j--; 63 } 64 if(j==0) break; 65 } 66 while(!s.empty()) 67 { 68 int put=s.top(); 69 s.pop(); 70 write(put); 71 printf(" "); 72 } 73 return 0; 74 }
请各位大佬斧正(反正我不认识斧正是什么意思)