BZOJ 1046: [HAOI2007]上升序列 LIS+贪心

title

BZOJ 1046
LUOGU 2215
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的上升序列。N<=10000,M<=1000

Output

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

Sample Input

6
3 4 1 2 3 6
3
6
4
5

Sample Output

Impossible
1 2 3 6
Impossible

analysis

\(Chdy\) 邀请,来写这道题。

第一眼望上去,以为是个 \(LIS\) 乱搞,现在想来,却也差不多是这样。

网上大部分人的代码是二分求 \(LIS\) 的,我也跟潮流。

但是第一份代码确实也是 \(O(N)\)\(LIS\) 的,然后对于无解的情况,我想到的是 \(O(N)\) 扫一遍,看我所求出的上升序列的长度中有没有题中的 \(L\),但是这样的复杂度是 \(O(NM)\),而且我也没处理好怎么记录每个长度的上升序列,所以就 gg 了。

被迫看题解,原来他们搞第二部分都是贪心搞得,那我就差不多会了,怎么找符合条件的数呢?只要他比上一个找到的数大,并且 以他开头的上升序列的长度 要比 还需要的长度 大(保证之后还能找到这个上升序列的其他数),每找到一个数,还需要的长度就减一,直至为 0 。

说了这么久都没说状态转移方程,其实是觉得不需要吧,毕竟 \(LIS\) 的状态转移应该是 \(DP\) 入门了吧(算了,还是列一下加深一下印象)。

\[F[i]=\max_{0\leq j<i~\&\&~A[j]<A[i]}\left\{F[j]+1\right\}\]

二分求 \(LIS\) 还是会快一些的。

code

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+10;

char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
    x=0;
    T f=1, ch=getchar();
    while (!isdigit(ch) && ch^'-') ch=getchar();
    if (ch=='-') f=-1, ch=getchar();
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}

template<typename T>inline void write(T x)
{
    if (!x) { putchar('0'); return ; }
    if (x<0) putchar('-'), x=-x;
    T num=0, ch[20];
    while (x) ch[++num]=x%10+48, x/=10;
    while (num) putchar(ch[num--]);
}

int f[maxn],a[maxn],e[maxn],tot;//f[i]表示以a[i]为开头的最长上升子序列的长度
inline int query(int x)
{
    int l=1,r=tot,ans=0;
    while (l<=r)
    {
        int mid=(l+r)>>1;
        if (e[mid]>x) ans=max(ans,mid),l=mid+1;
        else r=mid-1;
    }
    return ans;
}

int main()
{
    int n;read(n);
    for (int i=1; i<=n; ++i) read(a[i]);
    for (int i=n; i; --i)//状态设置,所以倒着搞
    {
        int now=query(a[i]);//二分多少数比a[i]大,即以a[i]为开头的上升序列
        f[i]=now+1;
        tot=max(tot,now+1);//记录上升序列的长度最大值
        e[now+1]=max(e[now+1],a[i]);//
    }
    int m;read(m);
    while (m--)
    {
        int l;read(l);
        if (l>tot) puts("Impossible");//如果这个长度比序列中最长上升序列的长度都要大,那么无解
        else
        {
            int last=0;//记录上一个找到的数
            for (int i=1; i<=n; ++i)
                if (f[i]>=l && a[i]>last)//之后又找到的数一定要比他大
                {
                    write(a[i]),putchar(' ');
                    --l;
                    last=a[i];
                    if (!l) break;
                }
            puts("");
        }
    }
    return 0;
}

summary

  1. \(DP\)\(OI\) 中是占有很大地位的,所以这一部分必须练好。
  2. 怎么练好也很关键,\(WY\) 曾经说对于 \(DP\) ,做题必须有一定量,不然看不出模型,不能抽象出以往做过的题,所以 \(DP\) 题要多练,也要找好题练。
  3. 熟悉模型,理解透彻,一个简单的模型可以出成一道极其毒瘤的题目。

猜你喜欢

转载自www.cnblogs.com/G-hsm/p/11327188.html