2019.01.28【洛谷P5156】[USACO18DEC]Sort It Out(LIS计数)(贪心)

传送门


解析:

不会树状数组进行LIS计数的小伙伴可以先看一下这篇博客:https://blog.csdn.net/zxyoi_dreamer/article/details/86682734

然后,这道题为什么要用LIS计数呢?

首先考虑我们已经选出来一个集合了,我们对这个集合进行各种操作,显然集合里面的元素最终都一定能够到达自己该到的位置。

但是,这个集合外的数的相对位置是显然没有改变过的。。。

换句话说,不管我们怎么选择集合,集合外的数的相对位置不会变化,要做到最后排序成功,必须集合外的数本身就是升序排列。

要求集合元素尽可能少,那么补集元素就要尽可能多。

那么补集是原序列的一个上升序列,同时又要尽可能大,所以就是LIS。

而我们要求原集合的字典序第 K K K小,那么补集的字典序就是所有补集中字典序第 K K K大,显然这是一个对偶问题。

所以我们的问题最终就转化为了求字典序第 K K K大的 L I S LIS LIS

考虑倒序处理,为每个长度开一个vector,存储有哪些位置开头的LIS长度为 i i i

然后贪心找出第 K K K大的LIS就行了。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
    inline char get_char(){
        static cs int Rlen=1<<20|1;
        static char buf[Rlen],*p1,*p2;
        return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
    }
    
    inline int getint(){
        re char c;
        while(!isdigit(c=gc()));re int num=c^48;
        while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
        return num;
    }
    
    inline ll getll(){
        re char c;
        while(!isdigit(c=gc()));re ll num=c^48;
        while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
        return num;
    }
}
using namespace IO;

cs ll lim=1e18+5;
cs int N=100005;
int n;ll k;

struct node{
    int len;
    ll cnt;
    void operator+=(cs node &b){
        if(len>b.len)return;
        if(len<b.len)*this=b;
        else cnt=min(cnt+b.cnt,lim);
    }
}t[N],f[N];

#define lowbit(x) (x&(-x))
inline void update(int pos,cs node &a){
    for(;pos;pos-=lowbit(pos))t[pos]+=a;
}

inline node query(int pos){
    node res=(node){0,1};
    for(;pos<=n;pos+=lowbit(pos))res+=t[pos];
    return res;
}

int a[N];
vector<int> vec[N];
bool vis[N];

signed main(){
    n=getint(),k=getll();
    for(int re i=1;i<=n;++i)a[i]=getint();
    for(int re i=n;i;--i){
        f[i]=query(a[i]+1);
        ++f[i].len;
        vec[f[i].len].push_back(i);
        update(a[i],f[i]);
    }
    int m=query(1).len;
    for(int re now=m,R=0;now;--now){
        for(int re i=vec[now].size()-1;~i;--i){
            int pos=vec[now][i];
            if(f[pos].cnt<k)k-=f[pos].cnt;
            else {
                vis[a[pos]]=true;
                while(R<pos)f[++R].cnt=0;
                break;
            }
        }
    }
    cout<<n-m<<"\n";
    for(int re i=1;i<=n;++i)if(!vis[i])cout<<i<<"\n";
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值