可持久化字典树
对于数列的一段区间,或是树上的路径,从中找出一个数,使得它异或z的值最大。对于其他相似的,可以通过前缀异或和的方式转化成此类。
基本思想:
- 以每个结点为根,建一颗字典树(内容为1到i的值)。这样之后,做差后,即为一段区间或是一段路径。
- 可以发现,如果裸着建,不仅要消耗很多的时间,更是要消耗很多的空间。考虑以i为根的字典树和以(i-1)为跟的字典树的异同。
- 可以发现,在当前以i为根的字典树上减去a[i],就是(i-1)的字典树了。所以,我们可以将除了a[i]之外的结点都连到(i-1)的树上。
当然,i-1的树也是从i-2的树上引用过来的。
- 这样之后,会出现一个问题。比如,a[i-1]的值与a[i]的值相同,那么我们到底要不要跟新呢?还是直接将root[i]全部指向root[i-1]?
还是之前的做法。除了a[i]的值,其他的值全部引用前面的,将a[i]跟新root[i]。
这样做完之后,可以发现,如果不加以区分,root[i]和root[i-1]的树一模一样,直接做差之后不就没有了。可是事实上,在【i,i】这给区间内,有一个数的值为a[i]。
所以我们用一个sum数组加以区别,也就是说,如果当前的root的结点是引用前面的,那么sum不变。否则(就是a[i]跟新的部分),sum[root[i]]=sum[[root[i-1]]+1。a[i]这个数的每个位置都是之前的+1。 - 这样我们在判断区间的时候,就可以拿sum[root[r]]-sum[root[l-1]]。如果大于0,说明在root[l-1]之后,被跟新过,所以可以取。如果sum值相同,代表root[r]中的那部分是直接引用的root[l-1]中的那部分,这个区间内没有跟新过,所以无法取。
bzoj 3261
给定一个非负整数序列{a},初始长度为N。
有M个操作,有以下两种操作类型:
1、Ax:添加操作,表示在序列末尾添加一个数x,序列的长度N+1。
2、Qlrx:询问操作,你需要找到一个位置p,满足l<=p<=r,使得:
a[p] xor a[p+1] xor ... xor a[N] xor x 最大,输出最大是多少。查询的部分为sum[p-1] xor sum[N] xor x。sum[N] xor x 直接可得,所以即寻找一个p-1,使得结果最大(l<=p<=r)。
- 直接那前缀异或和做字典树,由于最开始插入了root[1]的值为0,也就是root[1]代表的是sum[0],所以我们本来应该是查找root[l-2],root[r-1],现在应该是root[l-1],root[r]。
- 查询即可。
//要查询的是sum[n]^sum[k-1]^x 最大。 //从k开始异或,那么l-1<=k-1<=r-1 所以看以r-1为根的树是在以l-2为根的树上跟新过 // 所以应该是root[r],root[l-1] #include"stdio.h" #define N 30050000 int tree[N][2]; int root[N]; int l; int sum[N]; int d[40]; int cnt; void split(int k){ int i; l=0; while(k>0) { l++; d[l]=k%2; k=k/2; } for (i=l+1;i<=27;i++) d[i]=0; return; } void build(int x,int &y){ int i; cnt++; y=cnt; sum[y]=sum[x]+1; int now=y; for (i=27;i>=1;i--) { tree[now][d[i]^1]=tree[x][d[i]^1]; cnt++; tree[now][d[i]]=cnt; now=cnt;x=tree[x][d[i]]; sum[now]=sum[x]+1; } } int query(int x,int y) { int ans=0; int i; for (i=27;i>=1;i--) { if (sum[tree[y][d[i]^1]]-sum[tree[x][d[i]^1]]>0) { ans=ans+(1<<(i-1)); y=tree[y][d[i]^1];x=tree[x][d[i]^1]; }else { y=tree[y][d[i]];x=tree[x][d[i]]; } } return ans; } char s[10]; int main(){ int n,m,l,r,sum,x,k,i; int ans; cnt=0; scanf("%d %d",&n,&m); split(0); tree[0][0]=0; tree[0][1]=0; root[0]=0; build(root[0],root[1]); n++; sum=0; for (i=2;i<=n;i++) { scanf("%d",&x); sum=sum^x; split(sum); build(root[i-1],root[i]); } for (i=1;i<=m;i++) { scanf("%s",s); if (s[0]=='A') { n++; scanf("%d",&x); sum=sum^x; split(sum); build(root[n-1],root[n]); }else { scanf("%d %d %d",&l,&r,&k); k=k^sum; split(k); ans=query(root[l-1],root[r]); printf("%d ",ans); } } }
hdu 4757
- 给定一棵树,树上每个点都有一个权值,给两个点,问从l到r的路径上那个点异或z的值最大。
可持久化字典树+lca。
可以发现,树可以本拆分为一条一条从根到叶子的链。但这儿,不用这么麻烦。直接root[i]引用root[father[i]]即可。最后的答案为max(query(root[father[rmq(l,r)]],root[l]),query(root[father[rmq(l,r)]],root[r])),也就是树上的两个区间的最大值。
/* 有一棵树,树上每个点都有一个权值,给两个点,问从l到r的路径上那个点异或z的值最大。 把每个当成是一颗字典树,从当前点向father连边,跟新自己 */ #include"stdio.h" #include"string.h" #define N 10050000 int tree[N][2]; int root[N]; int ll; int sum[N]; int d[40]; int cnt; struct node{ int v,next; }edge[200050]; int head[100050]; int dp[1000050][30];//rmq int find[1000050];//保存搜索的路径 用来rmq int visit[100050]; int l,count; int start[100050];//保存每个点最开始被搜索到的位置 int rk[100050]; int id[100050]; int father[100050]; int a[100050]; int ss; int min(int a,int b){ if (a>b) return b;return a; } int max(int a,int b) { if (a>b) return a;return b; } void add(int x,int y){ count++; edge[count].v=y; edge[count].next=head[x]; head[x]=count; } void dfs(int k){ int i,v; if (rk[k]==-1) { ss++; rk[k]=ss; id[ss]=k; } for (i=head[k];i!=-1;i=edge[i].next){ v=edge[i].v; if (visit[v]==0){ father[v]=k; visit[v]=1; l++;find[l]=rk[k];if (start[k]==-1) {start[rk[k]]=l;} dfs(v); } } {l++;find[l]=rk[k];if (start[rk[k]]==-1) {start[rk[k]]=l;}} } void initrmq(int n){ int i,j; for (i=1;i<=n;i++) dp[i][0]=find[i]; for (j=1;(1<<j)<=n;j++) for (i=1;(i+(1<<j))-1<=n;i++) dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]); } int rmq(int l,int r){ int k; k=0; while(((1<<(k+1))+l-1)<=r) k++; return min(dp[l][k],dp[r-(1<<k)+1][k]); } void split(int k){ int i; ll=0; while(k>0) { ll++; d[ll]=k%2; k=k/2; } for (i=ll+1;i<=25;i++) d[i]=0; return; } void build(int x,int &y){ int i; cnt++; y=cnt; sum[y]=sum[x]+1; int now=y; for (i=25;i>=1;i--) { tree[now][d[i]^1]=tree[x][d[i]^1]; cnt++; tree[now][d[i]]=cnt; now=cnt;x=tree[x][d[i]]; sum[now]=sum[x]+1; } } int query(int x,int y) { int ans=0; int i; for (i=25;i>=1;i--) { if (sum[tree[y][d[i]^1]]-sum[tree[x][d[i]^1]]>0) { ans=ans+(1<<(i-1)); y=tree[y][d[i]^1];x=tree[x][d[i]^1]; }else { y=tree[y][d[i]];x=tree[x][d[i]]; } } return ans; } void ff(int k,int fa){ int i,v; for (i=head[k];i!=-1;i=edge[i].next) { v=edge[i].v; if (v!=fa) { split(a[v]); build(root[k],root[v]); ff(v,k); } } } char s[10]; int main(){ int n,m,sum,x,y,z,lca,k,i,xx,yy,q; int ans1,ans2; while(scanf("%d %d",&n,&q)!=EOF){ cnt=0;count=0; memset(head,-1,sizeof(head)); for (i=1;i<=n;i++) { scanf("%d",&a[i]); } for (i=1;i<=n-1;i++) { scanf("%d %d",&x,&y); add(x,y); add(y,x); } l=0; memset(start,-1,sizeof(start)); memset(visit,0,sizeof(visit)); memset(rk,-1,sizeof(rk)); memset(id,-1,sizeof(id)); memset(father,-1,sizeof(father)); visit[1]=1; ss=0; father[1]=0; dfs(1); initrmq(l); split(a[1]); tree[0][0]=0; tree[0][1]=0; root[0]=0; build(root[0],root[1]); n++; ff(1,-1); for (i=1;i<=q;i++) { scanf("%d %d %d",&x,&y,&z); split(z); xx=start[rk[x]]; yy=start[rk[y]]; int tmp; if (xx>yy) {tmp=xx;xx=yy;yy=tmp;} lca=id[rmq(xx,yy)]; ans1=query(root[father[lca]],root[x]); ans2=query(root[father[lca]],root[y]); printf("%d ",max(ans1,ans2)); } } }