题目链接
原题解:
从前往后扫描序列,维护当前可能出现在答案中的候选序列。
每当我们遇到一个不在候选序列中的数$x$时,若当前序列中最后一个数$y$在未被扫描得部分中也出现过,且$y>x$,我们可以把$y$弹出候选序列。
重复此操作,然后把$x$加入候选队列。每个元素最多进栈出栈一次,时间复杂度为$O(n)$。
补充:
描述一个我的半暴力的做法。
设置一个序列$s$,初始在每种数最后出现的位置填$0$,其他填$0$。
尝试找$k$轮,每轮找出接下来填的数字。
用树状数组求$s$的后缀和来二分,找出后缀和最大的下标中最靠后的,设为$p$。
设上一次取数的位置为$pre$,那么本轮就可以在$[pre+1,p]$中暴力找最小的数字中的第一个,然后把它在$s$上的影响消去。
这个做法可以AC,但是用时1600ms,算最慢的一档。其他的算法有700ms,1000ms和1300ms三档。
这个复杂度乍一看不太对,因为我们每次会暴力在一个区间里找最小值。
仔细思考一下,说不出个所以然来,但感觉这个暴力找的区间长度和重合度都不会特别高……之类的?然后就能过。不过NOI.AC的数据强度不敢特别恭维……
代码(100分):
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> #include<queue> #include<map> #include<set> #define IL inline #define RG register #define _1 first #define _2 second using namespace std; #define RI RG int #define RC RG char const int N=2e6; IL void qr(RI &x){ x=0; RC ch=getchar(); while(!isdigit(ch)) ch=getchar(); for(;isdigit(ch);ch=getchar()) x=(x<<3)+(x<<1)+ch-'0'; } IL void qw(RI x,RC ch){ RI k=0,s[13]; for(;x;x/=10) s[++k]=x%10; for(;k;k--) putchar(s[k]+'0'); putchar(ch); } int n,k,a[N+3]; int v[N+3]; int s[N+3]; IL int lowbit(RI x){return x&(-x);} IL void mdf(RI p,RI x){ p=n-p+1; for(;p<=n;p+=lowbit(p)) s[p]+=x; } IL int qry(RI p){ p=n-p+1; RI ret=0; for(;p;p-=lowbit(p)) ret+=s[p]; return ret; } IL int sol(RI x){ RI l=1,r=n,mid,ans; while(l<=r){ mid=(l+r)>>1; if(qry(mid)>=x){ l=mid+1; ans=mid; } else r=mid-1; } return ans; } int ans[N+3]; int main(){ qr(n); qr(k); for(RI i=1;i<=n;i++) qr(a[i]); for(RI i=n;i>=1;i--) if(!v[a[i]]){ v[a[i]]=i; mdf(i,1); } a[n+1]=k+1; for(RI i=1,p,x,pre=0;i<=k;i++){ p=sol(k-i+1); x=n+1; for(RI j=pre+1;j<=p;j++) if(v[a[j]]&&a[j]<a[x]) x=j; ans[i]=a[x]; mdf(v[a[x]],-1); v[a[x]]=0; pre=x; } for(RI i=1;i<=k;i++) qw(ans[i],' '); return 0; }View Code