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;
}

猜你喜欢

转载自www.cnblogs.com/liankewei/p/11809805.html
今日推荐