HAOI 2007 上升序列
对于一个给定的S={a1,a2,a3,…,an},若有P={ax1,ax2,ax3,…,axm},满足(x1<x2<…<xm)且(ax1<ax2<…<axm)。那么就称P为S的一个上升序列。如果有多个P满足条件,那么我们想求字典序最小的那个。
任务
给出S序列,给出若干询问。对于第i个询问,求出长度为Li的上升序列,如有多个,求出字典序最小的那个(即首先x1最小,如果不唯一,再看x2最小……),如果不存在长度为Li的上升序列,则打印Impossible.
输入
第一行一个N,表示序列一共有N个元素
第二行N个数,为a1,a2,…,an
第三行一个M,表示询问次数。下面接M行每行一个数Li,表示要询问长度为Li的上升序列。
输出
对于每个询问,如果对应的序列存在,则输出,否则打印Impossible.
样例
lis.in
6
3 4 1 2 3 6
3
6
4
5
lis.out
Impossible
1 2 3 6
Impossible
数据范围
N<=10000
M<=1000
【算法简析】
首先很容易发现,如果Li的长度大于这个序列的LIS的长度,那么一定无解,否则一定有解。为方便讨论,下文只考虑有解情况。
暂且抛开“字典序最小”这个限制条件,即任何长度为Li的上升序列Pi都可以看作符合要求,那么题目的所有要求都可以由LIS来满足。这样我们就可以直接求这个序列的LIS,然后对于所有m次询问,用O(n)的时间输出,这样算法总的时间复杂度就是O(nlogn+nm)。
那么如果要求字典序最小又如何处理呢?其实我们完全可以在相同的时间复杂度内解决问题,只是需要一些转化。
注意到在求LIS的过程中,实际上我们得到的不只是LIS,而是每个数作为子序列末尾的所有最长上升序列。把问题换一个角度,假设我们知道以每个数a[i]作为开头的最长上升序列的长度lis[i],那么就很容易利用贪心思想构造出答案了。这个贪心成立的前提就是:只要以该元素为开头的LIS的长度大于等于Li,则一定有解。该结论是很显然的。
下面简述贪心的方法:
累加下标j,从左到右扫描序列,如果lis[j]≥Li,则答案序列的第Li个数就是a[j],并且Li减1。重复上述过程直到Li=0。最后将答案序列倒着输出即可。
这个贪心显然是成立的。由于j是从小到大逐次增加,所以第一个符合lis[j]≥Li的必然就是最优解。每次找到一个符合的元素,所需要的LIS的长度就会减少1。这样重复下去得到的必然是最优解。
由于只需要对序列做一遍扫描,时间复杂度最坏O(n)。构造答案的时间为O(nm),加上预处理求LIS的O(nlogn)的时间,总时间复杂度为O(nlogn+nm)。
细节:求以a[i]为开头的LIS比较麻烦,可以把序列反过来后求以a[i]为末尾的LDS(Longest Decreasing Subsequence),这样处理起来较方便。
1 #include<bits/stdc++.h> 2 const int MAXN=100010; 3 using namespace std; 4 int n,m,cnt,a[MAXN],f[MAXN],best[MAXN]; 5 int BinarySearch(int x){ 6 int lowerBound=1,upperBound=cnt,ans=0; 7 while(lowerBound<=upperBound){ 8 int mid=(lowerBound+upperBound)/2; 9 if(best[mid]>x){ 10 ans=mid; 11 lowerBound=mid+1; 12 } 13 else upperBound=mid-1; 14 } 15 return ans; 16 } 17 18 void solve(int x){ 19 int last=0; 20 for(int i=1;i<=n;i++) 21 if(f[i]>=x&&a[i]>last){ 22 printf("%d",a[i]); 23 if(x!=1) printf(" "); 24 last=a[i]; 25 x--; 26 if(!x) break; 27 } 28 printf("\n"); 29 } 30 31 void preDP(){ 32 for(int i=n;i>=1;i--){ 33 int t=BinarySearch(a[i]); 34 f[i]=t+1; 35 cnt=max(cnt,t+1); 36 if(best[t+1]<a[i]) 37 best[t+1]=a[i]; 38 } 39 } 40 41 int main(){ 42 scanf("%d",&n); 43 for(int i=1;i<=n;i++) 44 scanf("%d",&a[i]); 45 preDP(); 46 scanf("%d",&m); 47 for(int i=1;i<=m;i++){ 48 int len; 49 scanf("%d",&len); 50 if(len<=cnt) 51 solve(len); 52 else puts("Impossible"); 53 } 54 return 0; 55 }