Codeforces Round #956 (Div. 2) and ByteRace 2024 F. array-value(trie经典问题 二分+01字典树)

题目

t(t<=2e4)组样例,每次给定长度n(n<=1e5)序列a(0<=ai<=1e9)

对于长度>=2的区间[l,r],定义b[l,r]为区间[l,r]内ai异或aj(l<=i<j<=r)的最小值,

求整个数组b值的第k小,即从小到大第k个的值

思路来源

难题肉搏小组

题解

先把第k小转成第k大,

然后二分答案mid,求有多少区间[l,r]满足b值>=mid

对于(a[i]^a[j])>=mid,即不存在(a[i]^x)<mid的x,

枚举右端点,对于右端点r来说,已经得到了左端点l,

那么r+1对应的左端点只会在l右侧,具有单调性,

所以把r+1对应的值a[r+1]加入到trie树前,

先检查trie上是否有值违背规则,即(a[r+1]^x)<mid,

双指针单增,把这样的x从trie上删掉,得到新的l’,

遍历所有右端点即得到合法区间数,

满足数量则往大里二分,否则往小里二分

代码


#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<set>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef pair<int,int> P;
#define fi first
#define se second
#define pb push_back    
#define dbg(x) cerr<<(#x)<<":"<<x<<" ";
#define dbg2(x) cerr<<(#x)<<":"<<x<<endl;
#define SZ(a) (int)(a.size())
#define sci(a) scanf("%d",&(a))
#define pt(a) printf("%d",a);
#define pte(a) printf("%d\n",a)
#define ptlle(a) printf("%lld\n",a)
#define debug(...) fprintf(stderr, __VA_ARGS__)
using namespace std;
const int N=1e5+10,M=3e6+10;
int t,n,a[N];
ll k;
int tr[M][2],cnt,num[M];
void init(){
    cnt=0;
    memset(tr[0],0,sizeof tr[0]);
    num[0]=0;
}
void add(int x){
    int rt=0;
	for(int i=30;i>=0;--i){
        int v=x>>i&1;
		if(!tr[rt][v]){
            tr[rt][v]=++cnt;
            num[cnt]=0;
            memset(tr[cnt],0,sizeof tr[cnt]);
        }
		rt=tr[rt][v];
        num[rt]++;
	}
}
void del(int x){
    int rt=0;
	for(int i=30;i>=0;--i){
        int v=x>>i&1;
		rt=tr[rt][v];
        num[rt]--;
	}
}
bool ok(int mid){//(a[i]^a[j])>=mid 即不存在(a[i]^x)<mid的x 单调性 双指针
    ll ans=0;
    init();
    int now=1;
    rep(i,1,n){
        int rt=0;
        for(int j=30;j>=0;--j){
            int v=a[i]>>j&1,w=mid>>j&1,x=v^w;
            if(w==1){//0^0<1 1^1<1
                while(now<i && tr[rt][v] && num[tr[rt][v]]>0){
                    del(a[now++]);
                }
            }
            if(!tr[rt][x])break;
            rt=tr[rt][x];
        }
        ans+=i-now;
        //printf("mid:%d i:%d now:%d ans:%lld\n",mid,i,now,ans);
        add(a[i]);
        if(ans>=k)return 1;
    }
    return 0;
}
int main(){
    sci(t);
    while(t--){
        scanf("%d%lld",&n,&k);
        k=1ll*n*(n-1)/2-k+1;
        rep(i,1,n)sci(a[i]);
        int l=0,r=(1<<30)-1;
        while(l<=r){
            int mid=l+(r-l)/2;
            if(ok(mid))l=mid+1;
            else r=mid-1;
        }
        pte(r);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小衣同学

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

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

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

打赏作者

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

抵扣说明:

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

余额充值