【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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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); } } }
用前缀来维护线性基的信息,当询问( l, r )区间的异或最大和时,查询位置在 r 前的线性基,并验证他们的位置是否在 l 后。显然越靠近 r 的线性基能满足更多以 r 为右边界的区间,因此在 r 处优先存离他最近的。最高位1重复的线性基尝试填补更低位的空白。
因此用 f[r][i] 记录 r 处最高位1在 i 位的数,用 p[r][i] 记录这个数的位置。遍历一次将所有位置的线性基及信息更新,复杂度是O(n)的。
查询时,从高位检查该位是否有符合的线性基,不断贪心异或。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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); } } }