• 可持久化字典树


    可持久化字典树


    对于数列的一段区间,或是树上的路径,从中找出一个数,使得它异或z的值最大。对于其他相似的,可以通过前缀异或和的方式转化成此类。

    基本思想:

    1. 以每个结点为根,建一颗字典树(内容为1到i的值)。这样之后,做差后,即为一段区间或是一段路径。
    2. 可以发现,如果裸着建,不仅要消耗很多的时间,更是要消耗很多的空间。考虑以i为根的字典树和以(i-1)为跟的字典树的异同。
    3. 可以发现,在当前以i为根的字典树上减去a[i],就是(i-1)的字典树了。所以,我们可以将除了a[i]之外的结点都连到(i-1)的树上。

      当然,i-1的树也是从i-2的树上引用过来的。

    4. 这样之后,会出现一个问题。比如,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。
    5. 这样我们在判断区间的时候,就可以拿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));
      	}
      	}
      }
      
  • 相关阅读:
    C# 类型的创建
    C# 中4个访问符和8个修饰符详解
    C#命名空间详解namespace
    ContextMenuStrip 添加在窗体。点击右键不能显示问题解答
    C# 学习笔记 C#基础
    React-Navigation web前端架构
    Css animation 与 float 、flex 布局问题
    javaScript 工作必知(十一) 数组常用方法实现
    Vue 父子组件传值 props
    kafka 参数配置 1
  • 原文地址:https://www.cnblogs.com/nowheretrix/p/9341258.html
Copyright © 2020-2023  润新知