BZOJ5484: [Usaco2018 Dec]Sort It Out
5484: [Usaco2018 Dec]Sort It Out
https://www.lydsy.com/JudgeOnline/problem.php?id=5484
Sol.
考虑没有在被喊叫集合中的点,他们一定是上升的。
那么最小的集合大小就是n-最长上升子序列长度。
对于第二问,有个转化:因为给出的是排列,求第k小的集合相当于求第k大的最长上升子序列。
那么可以记f[i]表示以i为头的最长上升子序列长度,g[i]表示方案数,转移时一起转移。
用个vector存最长上升子序列长度为i的开头有哪些,然后从大到小贪心取。
有个技巧:树状数组可以反着用!
我们要把1~x取Max,查询x~n的最小值。
那么可以
for(int i=x;i;i-=i&-i)tr[i]=max(tr[i],v)
for(int i=x;i<=n;i+=i&-ii) sum=max(tr[i],sum)
画个图看看似乎只有取Max可以用
#include<cstdio> #include<iostream> #include<cstdlib> #include<cstring> #include<algorithm> #include<cmath> #include<vector> #define ll long long #define maxn 100005 #define inf 1e18 using namespace std; int n,f[maxn],a[maxn],tr[maxn],v,fl[maxn],cnt,p[maxn],ans[maxn]; ll g[maxn],k,way,w[maxn]; vector<int>G[maxn]; void Add(ll &x,ll y){ if(inf-y<x)x=inf; else x+=y; } void ask(int i,int p){ for(;i<=n;i+=i&-i){ if(tr[i]>v)v=tr[i],way=w[i]; else if(tr[i]==v)Add(way,w[i]); } } void add(int i){ for(;i;i-=i&-i){ if(v>tr[i])tr[i]=v,w[i]=way; else if(v==tr[i])Add(w[i],way); } } bool cmp(int a,int b){return a>b;} int main(){ cin>>n>>k; for(int i=1;i<=n;i++)scanf("%d",&a[i]),p[a[i]]=i; for(int i=n;i>=1;i--){ v=0,way=0; ask(a[i],1); v++;f[i]=v,g[i]=way; if(f[i]==1)g[i]=way=1; add(a[i]); G[f[i]].push_back(a[i]); } for(int i=1;i<=n;i++)sort(G[i].begin(),G[i].end(),cmp); int Max=0,mp=0; for(int i=n;i>=1;i--){ for(int j=0;j<G[i].size();j++){ if(G[i][j]<Max)continue; int pl=p[G[i][j]]; if(pl<mp)continue; if(g[pl]>=k){ fl[pl]=1;Max=max(Max,G[i][j]);mp=max(mp,pl); break; } k-=g[pl]; } } cnt=0; for(int i=1;i<=n;i++)if(!fl[i])ans[++cnt]=a[i]; sort(ans+1,ans+cnt+1); printf("%d\n",cnt); for(int i=1;i<=cnt;i++)printf("%d\n",ans[i]); return 0; }