bzoj 1046 [HAOI2007] 上升序列 题解

转载请注明出处:http://blog.csdn.net/jiangshibiao/article/details/21646459

【题目】

1046: [HAOI2007]上升序列

Time Limit: 10 Sec   Memory Limit: 162 MB
Submit: 2110   Solved: 667
[ Submit][ Status]

Description

对于一个给定的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.

Input

第一行一个N,表示序列一共有N个元素第二行N个数,为a1,a2,…,an 第三行一个M,表示询问次数。下面接M行每行一个数L,表示要询问长度为L的上升序列。

Output

对于每个询问,如果对应的序列存在,则输出,否则打印Impossible.

Sample Input

6
3 4 1 2 3 6
3
6
4
5

Sample Output

Impossible
1 2 3 6
Impossible
数据范围
N<=10000
M<=1000

【分析】N<=10000,这真是不上不下的数据范围,特别是在大视野的测评系统下(算总时限的),低空飞行应该也可以过。不过,我们还是来分析一下N(log(N))的做法。首先,它要输出字典序最小的。那么我们不妨把i倒着循环枚举(防止出现转移问题。比如4,1,2,4,询问的长度为2,很有可能输出1,2)设f[i]表示从第N位到第i位的最长不上升序列的个数,然后套用经典的算法。最后,在处理每个询问的时候,只需正着枚举一边,防止转移错误。

【代码】

#include<cstdio>
using namespace std;
int a[10001],n,cnt,i,p,len,x,b[10001],f[10001],max,m;
inline int erfen(int l,int r)
{
  if (l==r) return l;
  int mid=(l+r)/2;
  if (a[i]>=b[mid]) return erfen(l,mid);
  return erfen(mid+1,r);
}
inline void print(int len,int sta)
{
  for (int i=sta+1;i<=n;i++)
    if (f[i]>=len&&a[i]>a[sta])
    {
      if (len==1) {printf("%d\n",a[i]);return;}
      else printf("%d ",a[i]);
      print(len-1,i);
      break;
    }
}
int main()
{
  scanf("%d",&n);
  for (i=1;i<=n;i++)
    scanf("%d",&a[i]);
  b[1]=a[n];cnt=1;f[n]=1;
  for (i=n-1;i>0;i--)
  {
    p=erfen(1,cnt+1);
    b[p]=a[i];f[i]=p;if (p>cnt) cnt=p;
    if (cnt>max) max=cnt;
  } 
  scanf("%d",&m);
  for (i=1;i<=m;i++)
  {
    scanf("%d",&x);
    if (x>max) printf("Impossible\n");
    else print(x,0);
  }
  return 0;
}

你可能感兴趣的:(题解,dp,二分,bzoj)