[bzoj3166][HEOI2013]ALO

76 篇文章 0 订阅
8 篇文章 0 订阅

题目大意

现有一个序列,一段长度>1区间的权值为区间内的次大值与区间内除次大值外的数的异或最大值。
例如:9 2 1 4 7
次大值为7,7 xor 9=14最大。
保证序列内元素两两不同。N<=50000,每个元素都不超过10^9.

解决次大值

我们考虑枚举一个数作为次大值,然后求出其最大往左和最大往右,那么显然,需要在这个区间内寻找与其异或最大的数。注意序列内的最大值永远不可能作为次大值。
我们考虑从大到小排序,顺次插入线段树中。对于现在要插入的原来在第i位的元素,j为在1~i-1区间中最右元素所在位置,k为在1~j-1区间中最右元素所在位置。显然k+1~i这个区间的次大值为i。往右扩展情况类似。因此我们可以用n log n的时间处理出扩展情况。

异或最大值

为了方便,我们可以把一个元素的扩展情况分成两块,一块是往左扩展情况,另一块是往右扩展情况。
对于往左扩展情况,从左到右做。当前做到i,其往左最大扩展到j。那么我们需要在j+1~i-1中寻找最大异或数。我们可以想到贪心,因为所有元素不超过10^9,所以化为二进制最多30位,将前i-1个元素放入trie中,每次尽量走能使当前位异或后为1的那一侧,即可。
但注意我们需满足元素位置号>j,因此在trie中每个结点维护一个最大标号。只要最大标号>j,则这条路一定可以走下去。
再类似的倒过来做一遍。

参考程序

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
struct dong{
    int x,id;
};
const int maxn=50000+10,ws=30;
int tree[maxn*33][2],g[maxn*33],num[maxn*5],sum[maxn*5];
int a[maxn][33],b[maxn],s[maxn],ans[maxn],pre[maxn],suf[maxn];
dong c[maxn];
int i,j,k,l,r,t,n,m,tot,top;
void insert(int p,int t,int i){
    g[p]=i;
    if (t<0) return;
    if (!tree[p][a[i][t]]) tree[p][a[i][t]]=++tot;
    insert(tree[p][a[i][t]],t-1,i);
}
int find(int p,int t,int i,int j,int flag){
    if (t<0) return 0;
    if (!flag){
        if (tree[p][1-a[i][t]]&&g[tree[p][1-a[i][t]]]>j) return find(tree[p][1-a[i][t]],t-1,i,j,flag)+(1<<t)*(1-a[i][t]);
        else return find(tree[p][a[i][t]],t-1,i,j,flag)+(1<<t)*a[i][t];
    }
    else{
        if (tree[p][1-a[i][t]]&&g[tree[p][1-a[i][t]]]<j) return find(tree[p][1-a[i][t]],t-1,i,j,flag)+(1<<t)*(1-a[i][t]);
        else return find(tree[p][a[i][t]],t-1,i,j,flag)+(1<<t)*a[i][t];
    }
}
bool cmp(dong a,dong b){
    return a.x>b.x;
}
void change(int p,int l,int r,int a){
    if (l==r){
        sum[p]=num[p]=l;
        return;
    }
    int mid=(l+r)/2;
    if (a<=mid) change(p*2,l,mid,a);else change(p*2+1,mid+1,r,a);
    num[p]=max(num[p*2],num[p*2+1]);
    sum[p]=min(sum[p*2],sum[p*2+1]);
}
int get(int p,int l,int r,int a,int b,int flag){
    if (l==a&&r==b){
        if (flag) return num[p];else return sum[p];
    }
    int mid=(l+r)/2;
    if (b<=mid) return get(p*2,l,mid,a,b,flag);
    else if (a>mid) return get(p*2+1,mid+1,r,a,b,flag);
    else{
        int j=get(p*2,l,mid,a,mid,flag),k=get(p*2+1,mid+1,r,mid+1,b,flag);
        if (flag) return max(j,k);else return min(j,k);
    }
}
int main(){
    scanf("%d",&n);
    fo(i,1,n){
        j=0;
        scanf("%d",&t);
        b[i]=t;
        while (t){
            a[i][j]=t%2;
            t/=2;
            j++;
        }
        c[i].x=b[i];c[i].id=i;
    }
    sort(c+1,c+n+1,cmp);
    fo(i,1,4*n) sum[i]=n+1;
    fo(i,1,n){
        j=get(1,1,n,1,c[i].id,1);
        if (!j) pre[c[i].id]=0;
        else{
            if (j>1){
                k=get(1,1,n,1,j-1,1);
                j=k+1;
            }
            pre[c[i].id]=j;
        }
        j=get(1,1,n,c[i].id,n,0);
        if (j==n+1) suf[c[i].id]=n+1;
        else{
            if (j<n){
                k=get(1,1,n,j+1,n,0);
                j=k-1;
            }
            suf[c[i].id]=j;
        }
        change(1,1,n,c[i].id);
    }
    fo(i,1,n){
        k=find(0,ws,i,pre[i]-1,0);
        ans[i]=b[i]^k;
        insert(0,ws,i);
    }
    fo(i,0,tot) g[i]=n+1,tree[i][0]=tree[i][1]=0;
    fd(i,n,1){
        k=find(0,ws,i,suf[i]+1,1);
        ans[i]=max(ans[i],b[i]^k);
        insert(0,ws,i);
    }
    m=0;
    l=r=0;
    fo(i,1,n){
        if (pre[i]==0&&suf[i]==n+1) ans[i]=0;
        if (ans[i]>m){
            m=ans[i];
            l=pre[i];r=suf[i];
        }
    }
    printf("%d\n",m);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值