【HDOJ2019网络赛】1002 Operation

题目:http://acm.hdu.edu.cn/showproblem.php?pid=6579

 

大意是对长度为n的数列进行m次操作,操作0是询问从( l, r )区间选择若干数进行异或的最大值,操作1是将数x添加进数列末。

 

n的范围是1e6,每个数范围在int内。HDOJ的读入速度慢所以要写读入挂。_(:з」∠)_

 

关于异或线性基:

异或线性基资料参考

例如0001、0010、0100、1000可以通过异或表示出0 ~ 1111的任意数,也就是只要有数满足最高位为 i 位,就可以通过异或控制 i 位为1或者为0。

原因是在异或中:

最高位1在第一位的数(0001)可以控制与它异或的结果的第一位;

最高位1在第一位的数(0001)和最高位1在第二位(0010、0011)可以控制与它异或的结果的第一位和第二位;

最高位1在第一位的数(0001)、最高位1在第二位(0010、0011)和最高位1在第三位(0100、0101等)可以控制与它异或的结果的第一位、第二位和第三位;

......

 

那么在区间中是不是只要各个位选择一个最高位1在该位的数就可以了?

最高位1重复的数,可能依然是有用的,例如1001和1101,两者的最高位1相同,但异或后得到0100,填补了最高位1在第三位的空缺。因此对于最高位1重复的数,将它与之前记录的数异或,尝试填补更低位的空白。

尽可能地填补了空缺,得到了各位的异或线性基以后,可以用贪心求出区间若干数的异或最大值。从值最大的线性基(即最高位1最靠左)往小遍历,如果可以得到更大的值就异或。通过不断的选择,我们能保证更高位的数为1。

 

例如:

得到的线性基为11001、01100、00111、00010、00001.

1. 显然11001一定选择。

2. 11001与01100异或为10101,为了保证第四位为1,不选择与01100异或。

3. 11001与00111异或为11110,为了保证第三位为1,选择00111。

......

 

因此贪心的策略为 if( sum[i]^ans > ans )   ans = ans^sum[i] 

 

不难想到,用数据类型来维护区间最大异或和

二十分钟敲完线段树,当写到修改操作时我终于发现这个算法会TLE

因为每一次修改都会增加区间长度,对于整个树都有影响,只能重新建树

_(:з」∠)_你数数这是多少个log(本题时限4000ms

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N = 1000001;
int n,m,p;
long long a[N];
struct node{
    int l,r,sum;
}tree[4*N];
int q;
int L,R;
int ans;
long long judge(int now,long long x,long long y)
{
    long long z = max(x,y);
    long long f = x^y;
    if(f>z) return x^y;
    else return z;
}
long long qurry(int now,int l,int r)
{
    if(tree[now].l>r) return 0;
    if(tree[now].r<l) return 0;
    
    if(tree[now].l>=l&&tree[now].r<=r)
    {
        ans = judge(now,ans,tree[now].sum);
        return ans;
    }
     
    int mid=(tree[now].l+tree[now].r)/2;
    if(l<=mid) 
    {
        ans = judge(now,ans,qurry(now*2,l,r));
        return ans;
    } 
    if(r>=mid) 
    {
        ans = judge(now,ans,qurry(now*2+1,l,r));
        return ans;
    } 
}
void build(int now,int l,int r)
{
    tree[now].l=l;
    tree[now].r=r;
    if(l==r)
    {
        tree[now].sum=a[l];
        return ;
    }
    int mid=(l+r)/2;
    build(now*2,l,mid);
    build(now*2+1,mid+1,r);
    tree[now].sum=max(tree[now*2].sum,tree[now*2+1].sum);
    if(tree[now*2].sum^tree[now*2+1].sum>tree[now].sum) tree[now].sum=tree[now*2].sum^tree[now*2+1].sum;
}
int main()
{
    scanf("%d%d",&n,&m);
    p=n;
    for(int i=1;i<=n;i++) 
    {
        scanf("%lld",&a[i]);
    }
    build(1,1,n);
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&q);
        if(!q)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            L = (x^ans)%p+1;
            R = (y^ans)%p+1;
            ans = qurry(1,L,R);
            printf("%lld",ans);
        }
        else 
        {
            long long x;
            scanf("%lld",&x);
            a[++n]=x;
            build(1,1,n+1);
        }
    }
} 
View Code

 

 

用前缀来维护线性基的信息,当询问( l, r )区间的异或最大和时,查询位置在 r 前的线性基,并验证他们的位置是否在 l 后。显然越靠近 r 的线性基能满足更多以 r 为右边界的区间,因此在 r 处优先存离他最近的。最高位1重复的线性基尝试填补更低位的空白。

因此用 f[r][i] 记录 r 处最高位1在 i 位的数,用 p[r][i] 记录这个数的位置。遍历一次将所有位置的线性基及信息更新,复杂度是O(n)的。

查询时,从高位检查该位是否有符合的线性基,不断贪心异或。

#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1000001;
int n;
int f[N][32];
int p[N][32];
int ans;
void addin(int id,int a)
{
    /* 初始化第i位的信息 */
    for(int i=30;i>=0;i--) 
    {
        f[id][i]=f[id-1][i];
        p[id][i]=p[id-1][i];
    }
    
    /* pos是在这个位置的线性基的位置信息 */ 
    int pos=id;
    for(int i=30;i>=0;i--)
    {
        /* 满足最高位1为i */ 
        if(a>>i)
        {
            /* 有最高位1的数重复 */ 
            if(f[id][i])
            {
                /* 保留位置更靠近i的那一个 */ 
                /* 另一个数被异或,填补更低位空缺 */ 
                if(pos>p[id][i])
                {
                    int temp=pos;pos=p[id][i];p[id][i]=pos;
                        temp=f[id][i];f[id][i]=a;a=temp;
                }
                a^=f[id][i];
            }
            else 
            {
                p[id][i]=pos;
                f[id][i]=a;
                break;
            }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) 
    {
        int a;
        scanf("%d",&a);
        addin(i,a);
     } 
    for(int i=1;i<=m;i++)
    {
        int q;
        scanf("%d",&q);
        if(q)
        {
            int a;
            scanf("%d",&a);
            addin(a);
        }
        else 
        {
            int l,r;
            scanf("%d%d",&l,&r);
            l=(l^ans)%n+1;
            r=(r^ans)%n+1;
            ans=0;
            if(l>r)
            {
                int temp=l;l=r;r=temp;
            }
            for(int j=30;j>=0;j--) if((ans^f[r][j])>ans&&p[r][j]>=l) ans^=f[r][j];
            printf("%d",ans);
        }
    }
}
View Code

 

posted @ 2019-07-22 20:49  Oranges  阅读(189)  评论(0编辑  收藏  举报