Comet OJ - Contest #16 小 C 的可重集 (随机二分)(线段树)

传送门

  • 秀儿操作!我们每次随一个区间,考虑求出 ≥ \ge 它的区间个数
    注意到左端点固定区间大小随右端点递增,于是 ≥ \ge 一个区间的是一个前缀
    如果个数 ≥ k \ge k k 那么可以给每一个左端点的区间钦定一个上界,否则可以钦定一个下界
    这样的期望次数是 log ⁡ n \log n logn 的,相当于每次 b a n ban ban 掉一部分
    于是问题变成了对于给定区间,对每一个左端点求前缀的终止位置使得这个前缀 ≥ \ge 给点区间
    这个位置单调,于是我们可以 t w o   p o i n t e r two\ pointer two pointer 扫一下,线段树维护值域
#include<bits/stdc++.h>
#define cs const
#define pb push_back
using namespace std;
typedef long long ll;
template <typename T> T read(){
    T cnt=0, f=1; char ch=0;
    while(!isdigit(ch)){ ch=getchar(); if(ch=='-') f=-1; }
    while(isdigit(ch)) cnt=cnt*10+(ch-'0'), ch=getchar();
    return cnt*f;
}
std::mt19937 rn(time(0));
std::uniform_int_distribution<int> rnd(0,(int)1e9);
int gi(){ return read<int>(); }
ll gl(){ return read<ll>(); }
cs int N = 1e5 + 50;
int n, a[N]; ll k;
int lim[N];
namespace SGT{
    cs int M = 1<<17;
    int c[M<<1|5]; bool les[M<<1|5];
    void clr(){ 
        memset(c,0,sizeof(c));
        memset(les,0,sizeof(les));
    }
    void pushup(int x){
        c[x]=c[x<<1]|c[x<<1|1];
        les[x]=les[x<<1]|(!c[x<<1]&&les[x<<1|1]);
    }
    void add(int x, int v){
    	if(!x) return;
        c[x+=M]+=v; les[x]=c[x]<0;
        for(x>>=1;x;x>>=1) pushup(x);
    }
}
ll work(int l, int r){
    SGT::clr(); ll ct = 0;
    for(int i=l; i<=r; i++) SGT::add(a[i],1);
    for(int i=1; i<=n; i++){
        lim[i]=max(i-1,lim[i-1]);
        while(!SGT::les[1]&&lim[i]<=n) 
            SGT::add(a[++lim[i]],-1);
        if(i<=lim[i]) SGT::add(a[i],1);
        ct += (ll)lim[i]-i;
    } return ct;
}
int main(){
    n=gi(), k=(ll)n*(n+1)/2-gl()+1;
    for(int i=1; i<=n; i++) a[i]=gi();
    static int st[N]; int tp=0; 
    static int L[N], R[N];
    for(int i=1; i<=n; i++)
    st[++tp]=i, L[i]=i, R[i]=n;
    int T = 80;
    while(T--){
        int c=st[rnd(rn)%tp+1];
        int t=L[c]+rnd(rn)%(R[c]-L[c]+1);
        if(work(c,t)>=k) 
            for(int i=1; i<=tp; i++) 
                R[st[i]]=min(R[st[i]],lim[st[i]]-1);
        else 
            for(int i=1; i<=tp; i++)
                L[st[i]]=max(L[st[i]],lim[st[i]]);
        int ct=0; 
        for(int i=1; i<=tp; i++) 
        if(L[st[i]]<=R[st[i]]) st[++ct]=st[i]; tp=ct; 
        if(tp==1&&L[st[1]]==R[st[1]]) break;
    }
    int l=st[1], r=R[st[1]];
    sort(a+l,a+r+1);
    for(int i=l; i<=r; i++) cout<<a[i]<<" ";
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值