• [BZOJ 3731] Gty的超级妹子树 (树分块)


    [BZOJ 3731] Gty的超级妹子树 (树分块)

    题面

    给出一棵树(或森林),每个点都有一个值。现在有四种操作

    1.查询x子树里>y的值有多少个

    2.把点x的值改成y

    3.添加一个新节点,它的父亲是x,值是y

    4.断开节点x与父亲节点的边,一棵树变成两棵树

    分析

    新姿势-树分块get

    分块预处理

    如果没有树的形态变化,一棵主席树就可以了。但是有形态变化,我们考虑树分块。树分块的思路是把树分成许多大小相等的树,可以用dfs预处理出来。我们dfs的时候把当前块的所有值记录在一个vector里,当我们dfs到一个儿子y的时候,如果当前块大小>我们设定的最大大小,就新建一个块。

    注意vector里要排好序,具体方法就是每次插入的时候找到原来的序列中它应该插入的位置,然后再插入

    同时,我们要新建一棵新树,新树的节点等于块数,相当于把每个块缩成一个点。每次新建块的时候向父亲节点的所在块连边即可得到新树。

    我们需要维护一下几个信息:

    int bcnt=0;//块的数量
    int bsz;//设定的块大小 
    int bel[maxn+5];//x属于哪个块 
    int fa[maxn+5];//节点x的父亲 
    int root[maxn+5];//第x块对应的子树的根 
    vector<int>num[maxn+5];//存储第x个块的值
    

    查询

    我们查询x的子树的时候,x可能被分成了多个块。对于每个整块,我们只要在num里面二分查找就可以找到>y的数的个数。如果x本身是某个整块的根节点,那直接二分查找就可以了。否则在原树上x的子树里dfs,如果dfs到的节点y是某个整块的根节点,那么在新树上dfs累加整块的答案,返回。对于不完整的块里的散点,直接暴力统计即可。

    修改值

    只需要修改所在块的num即可

    添加新节点

    分类讨论

    首先,在原树上添加一条边

    1.若加入新节点后父亲节点所在块大小没有超过最大块大小,只需要更新一下块里的num

    2.否则新建一个块,类似预处理里面新建块的方法维护每个块的答案

    分成两棵树

    这种情况比较复杂。

    首先,删掉原树上和父亲节点相连的边

    1.若x是某个整块的根节点,删除新树上x对应的块和父亲节点对应的块的边

    2.若x不是某个整块的根节点。在原树上dfs,找出x的子树里和x在同一个块的点,和x子树里的整块。把x新建成一个块,把散点全部插入新的块中,同时删除新树上x对应的块和父亲节点对应的块的边,删掉新树上x原来的块和x子树里的整块相连的边,新的块向dfs的时候找到的x子树里的整块连边

    时间复杂度分析

    树分块的时间复杂度没有保证,但数据随机的情况下为(O(m sqrt n log n)),块大小取(sqrt nlog n)较优秀

    代码

    #include<iostream>
    #include<cstdio>
    #include<vector>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #define maxn 100000
    #define maxm 100000
    using namespace std;
    inline void qread(int &x) {
    	x=0;
    	int sign=1;
    	char c=getchar();
    	while(c<'0'||c>'9') {
    		if(c=='-') sign=-1;
    		c=getchar();
    	}
    	while(c>='0'&&c<='9') {
    		x=x*10+c-'0';
    		c=getchar();
    	}
    	x=x*sign;
    }
    inline void qprint(int x) {
    	if(x<0) {
    		putchar('-');
    		qprint(-x);
    	} else if(x==0) {
    		putchar('0');
    		return;
    	} else {
    		if(x>=10) qprint(x/10);
    		putchar('0'+x%10);
    	}
    }
    
    int n,m;
    int a[maxn+5];
    vector<int>E1[maxn+5],E2[maxn+5];//E1原树,E2新书
    vector<int>num[maxn+5];//存储第x个块的值
    void vec_insert(vector<int>&x,int y){//插入y,且不破坏从小到大的序列 
    	vector<int>::iterator it=lower_bound(x.begin(),x.end(),y+1);
    	if(it==x.end()) x.push_back(y);
    	else x.insert(it,y);
    }
    void vec_del(vector<int>&x,int y){//删除y 
    	vector<int>::iterator it=lower_bound(x.begin(),x.end(),y);
    	if(it!=x.end()) x.erase(it);
    }
    //注意尽量多判边界,否则容易RE,比如删除的时候可能父节点不存在,这时如果没判x.end()就会RE
    //在这里判边界,比操作的时候判方便
    void vec_modify(vector<int>&x,int last,int now){//把last修改成now 
    	vec_del(x,last);
    	vec_insert(x,now);
    }
    
    
    void add_edge(vector<int>* E,int x,int y){
    	E[x].push_back(y);
    } 
    void del_edge(vector<int>*E,int x,int y){
    	vec_del(E[x],y);
    }
    
    int bcnt=0;//块的数量
    int bsz;//设定的块大小 
    int bel[maxn+5];//x属于哪个块 
    int fa[maxn+5];//原树上节点x的父亲 
    int root[maxn+5];//第x块对应的子树的根 
    void dfs1(int x,int f){
    	fa[x]=f;
    	bel[x]=bcnt;
    	vec_insert(num[bel[x]],a[x]);
    	for(int y : E1[x]){
    		if(y!=f){
    			if((int)num[bel[x]].size()==bsz){
    				bcnt++;
    				root[bcnt]=y; 
    				add_edge(E2,bel[x],bcnt);//由于我们知道新树上父亲和儿子的关系,直接建有向边就好了,这样dfs的时候比较方便
    			}
    			dfs1(y,x);
    		}
    	}
    }
    
    int ans=0;
    int get_ans(int id,int val){
    	return num[id].end()-upper_bound(num[id].begin(),num[id].end(),val);
    } 
    void dfs2(int idx,int val){//处理整块子树 
    	ans+=get_ans(idx,val);
    	for(int y : E2[idx]){
    		dfs2(y,val);
    	} 
    }
    void dfs3(int x,int val){
    	if(a[x]>val) ans++;//处理散点 
    	for(int y : E1[x]){
    		if(y!=fa[x]){
    			if(bel[x]==bel[y]) dfs3(y,val);
    			else dfs2(bel[y],val);
    		}
    	}
    }
    
    vector<int>nd;//需要重构的点 (x子树和x在同一个块的点)
    vector<int>bk;//需要重构的块 (x子树里的整块)
    void dfs4(int x){
    	nd.push_back(x);
    	for(int y : E1[x]){
    		if(y!=fa[x]){
    			if(bel[y]==bel[x]) dfs4(y);
    			else bk.push_back(bel[y]);
    		} 
    	}
    }
    int query(int x,int val){
    	ans=0;
    	if(root[bel[x]]==x) dfs2(bel[x],val);
    	else dfs3(x,val);
    	return ans;
    }
    void change_val(int x,int val){
    	vec_modify(num[bel[x]],a[x],val);
    	a[x]=val;
    }
    void add_point(int f,int val){
    	a[++n]=val;
    	add_edge(E1,f,n);
    	add_edge(E1,n,f);
    	fa[n]=f;
    	if((int)num[bel[f]].size()==bsz){//如果大小超过bsz,就新建一块 
    		bel[n]=++bcnt;
    		root[bcnt]=n;
    		vec_insert(num[bel[n]],a[n]);
    		add_edge(E2,bel[f],bel[n]);
    	}else{//否则插入 
    		bel[n]=bel[f];
    		vec_insert(num[bel[n]],a[n]); 
    	}
    }
    
    void split(int x){
    	if(root[bel[x]]==x){//正好自成一块 
    		if(fa[x]){
    			del_edge(E1,x,fa[x]);//删掉原树上的边
    			del_edge(E1,fa[x],x);
    			del_edge(E2,bel[fa[x]],bel[x]);//删掉新树上的边
    		}
    	}else{
    		del_edge(E1,x,fa[x]);//删掉原树上的边
    		del_edge(E1,fa[x],x);
    		bk.clear();
    		nd.clear();
    		dfs4(x);
    		vec_del(num[bel[x]],a[x]);//x特殊处理一下
    		bel[x]=++bcnt;
    		vec_insert(num[bel[x]],a[x]);
    		for(int u : nd){
    			if(u==x) continue;
    			vec_del(num[bel[u]],a[u]);
    			vec_insert(num[bcnt],a[u]);//把散点全部插入新的块中
    			bel[u]=bcnt;
    		}
    		for(int t : bk){
    			del_edge(E2,bel[fa[x]],t);
    			add_edge(E2,bcnt,t);//新块向dfs的时候找到的x子树里的整块连边
    		}
    	} 
    }
    int main(){
    	int cmd;
    	int x,y;
    	int last=0;
    	qread(n);
    	bsz=sqrt(n)*log(n);
    	for(int i=1;i<n;i++){
    		qread(x);
    		qread(y);
    		add_edge(E1,x,y);
    		add_edge(E1,y,x);
    	}
    	for(int i=1;i<=n;i++) qread(a[i]);
    	bcnt=1;
    	root[1]=1;
    	dfs1(1,0);
    	qread(m);
    	for(int i=1;i<=m;i++){
    		qread(cmd);
    		if(cmd==0){
    			qread(x);
    			qread(y);
    			x^=last;
    			y^=last;
    			last=query(x,y);
    			qprint(last);
    			putchar('
    ');
    		}else if(cmd==1){
    			qread(x);
    			qread(y);
    			x^=last;
    			y^=last;
    			change_val(x,y);
    		}else if(cmd==2){
    			qread(x);
    			qread(y);
    			x^=last;
    			y^=last;
    			add_point(x,y); 
    		}else{
    			qread(x);
    			x^=last;
    			split(x);
    		}
    	}
    }
    
    
  • 相关阅读:
    常用正则搜集(已验证)
    oracle 如何跨用户查询数据
    SVN状态图标消失的解决方法
    oracle 简单列操作
    正则替换行尾,行末内容
    怎么解决svn清理失败且路径显示乱码问题
    Oracle坑爹入门踩坑篇
    如何过滤datable?
    JS产生模态窗口,关闭后刷新父窗体。(兼容各浏览器)
    6 Jmeter脚本组成和组件搭配
  • 原文地址:https://www.cnblogs.com/birchtree/p/11323540.html
Copyright © 2020-2023  润新知