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