zoukankan      html  css  js  c++  java
  • 点分树

    P6329 【模板】点分树 | 震波

    就就着这个模板题讲一下吧

    题意:求在(x)距离(k)以内的所有点的点权,点权可以修改

    先考虑简化问题:只处理以一个固定点为中心的答案

    我们需要用一些数据结构维护一些信息,维护什么呢?

    考虑查询时是查询到这个点的距离为(0- k)的点权之和,即求前缀和

    那么维护的信息就是:到当前根的距离在([0,k])范围内的点的点权,每次修改是单点修改,查询前缀和

    那么这道题可以用树状数组(因为边权为(1),所以取值一定是连续的,不存在空的点浪费空间的情况)

    但是可能边权稍微大了一点树状数组空间就会爆炸,那么就要用动态开点线段树(好像没啥用)

    struct arr{
    	vector<int> v;
    	inline int& operator[](int x){//注意到每个树状数组的大小不一样,需要动态开(下面会说到)
    		while(x>=v.size())v.push_back(0);
    		return v[x];
    	}
    };
    inline int lowbit(int x){return x&(-x);}
    struct BIT{
    	arr val;
    	inline void modify(int n,int pos,int x){//pos可以取0,但是树状数组不支持,必须同时加上一个1
    		++n;++pos;while(pos<=n)val[pos]+=x,pos+=lowbit(pos);	
    	}
    	inline int query(int n,int pos){
    		++pos;pos=min(n+1,pos);int ret=0;
    		while(pos)ret+=val[pos],pos-=lowbit(pos);
    		return ret;
    	}
    }T1[N],T2[N];
    

    接下来考虑多个点询问的情况

    那么每个点的改变都会导致所有节点的树状数组发生改变,暴力修改是(O(n^2log n))

    那么显然不能更改全部的点,那么只能更改一部分查询的时候加和

    那么就需要容斥一下

    中间的部分被算了两次,需要被剪掉一次

    根据点分治的基本思想,每次把树分成(log)的大小

    样例的这个图

    按这个找根的顺序重新建树

    最大的树的重心(3)控制所有点

    然后(2)控制(2,3,4,5)

    以此类推

    namespace Divide{
    	int size[N],sz[N],rt,mn;
    	bool vis[N];
    	void get_size(int u,int fa){
    		size[u]=1;for(int i=head[u],v;i;i=e[i].next){
    			v=e[i].to;if(v==fa||vis[v])continue;
    			get_size(v,u);size[u]+=size[v];
    		}
    	}
    	int get_dep(int u,int fa,int d){
    		int ret=d+1;for(int i=head[u],v;i;i=e[i].next){
    			v=e[i].to;if(v==fa||vis[v])continue;
    			ret=max(ret,get_dep(v,u,d+1));
    		}
    		return ret;
    	}
    	void dfs1(int u,int fa,int S){
    		int cur=S-size[u];for(int i=head[u],v;i;i=e[i].next){
    			v=e[i].to;if(v==fa||vis[v])continue;
    			dfs1(v,u,S);cur=max(cur,size[v]);
    		}
    		if(cur<mn)mn=cur,rt=u;
    	}
    	inline int root(int u){get_size(u,u);mn=inf,rt=u;dfs1(u,u,size[u]);return rt;}
    	int fa[N];
    	void build(int u,int father){
    		int x=root(u);
    		sz[x]=get_dep(x,x,-1);fa[x]=father;u=x;vis[u]=1;
    		for(int i=head[u],v;i;i=e[i].next){
    			v=e[i].to;if(vis[v]||v==father)continue;
    			build(v,u);
    		}
    	}
    }
    

    修改与查询操作

    • 查询

    先看需要查询哪些东西,再去看要维护些啥

    首先在重新建的树中依次跳父亲

    起点:自己,直接把与自己距离小于等于(k)的加上即可

    然后依次跳父亲,再加上与自己距离小于等于(k-dis(cur,start))的点的点权

    但是有一部分是既在与上一层(k)的距离以内,又在现在这一层(k-dis(cur,start))的距离以内,必须减掉。即减去下一层子树中到当前根的距离小于等于(k-dis(cur,start))的权值和

    • 距离(dis)(ST)(O(1))求法

    每次(O(log n))找LCA太慢,用一种(O(nlog n))预处理,(O(1))查询的ST表方法

    对树进行DFS

    那么查询(x,y)间的LCA:查询两个点第一次出现([id[x],id[y]])间的深度最小的那个点,这个点就是LCA

    namespace Graph{
    	int inde=0,st[N<<2][LOG],Log2[N<<2],dep[N],id[N];
    	inline void dfs(int u,int fa){
    		st[++inde][0]=u;dep[u]=dep[fa]+1;id[u]=inde;
    		for(int i=head[u],v;i;i=e[i].next){v=e[i].to;if(v==fa)continue;dfs(v,u);st[++inde][0]=u;}
    	}
    	inline int get_min(int x,int y){return dep[x]<dep[y]?x:y;}
    	inline void pre_work(){
    		for(int i=1;i<=inde;++i)Log2[i]=Log2[i-1]+(1<<Log2[i-1]==i);
    		for(int i=1;i<Log2[inde];++i)
    			for(int j=1;j+(1<<i)<=inde;++j)
    				st[j][i]=get_min(st[j][i-1],st[j+(1<<(i-1))][i-1]);
    	}
    	inline int LCA(int x,int y){
    		if(x==y)return x;
    		x=id[x],y=id[y];if(x>y)swap(x,y);
    		int l=Log2[y-x+1]-1;
    		return get_min(st[x][l],st[y-(1<<l)+1][l]);
    	}
    	inline int dis(int x,int y){return dep[x]+dep[y]-2*dep[LCA(x,y)];}
    	inline void init(){dfs(1,0);pre_work();}
    }
    

    至此完成了(query)

    inline int query(int x,int k){
    	int ans=0,start=x,pre=0;
    	while(x){
    		int dis=Graph::dis(x,start);
    		if(dis<=k){
    			ans+=T1[x].query(k-dis);
    			if(pre)ans-=T2[pre].query(k-dis);
    		}
    		pre=x,x=fa[x];
    	}
    	return ans;
    }
    
    • 修改

    对于每个点,依次修改控制得到它的所有重心的贡献

    那么按照上面的查询的需要,更新当前子树的重心的同时,还要更新上一层更大子树的根的贡献(为了去重)

    inline void update(int x,int val){
    	int start=x;
    	while(x){
    		T1[x].modify(sz[x],Graph::dis(start,x),val);
    		if(fa[x])T2[x].modify(sz[fa[x]],Graph::dis(start,fa[x]),val);
    		x=fa[x];
    	}
    }
    

    主函数中:

    for(int i=1,u,v;i<n;++i)u=read(),v=read(),adde(u,v),adde(v,u);
    Graph::init();
    build(1,0);
    for(int i=1;i<=n;i++)update(i,value[i]);
    int lastans=0;
    int opt,x,y;
    while(m--){
    	opt=read(),x=read()^lastans,y=read()^lastans;
    	if(opt==0)printf("%d
    ",lastans=query(x,y));
    	else{
    		int delta=y-value[x];
    		update(x,delta);
    		value[x]=y;
    	}
    }
    
  • 相关阅读:
    HTML5 Input 类型
    Html5 web 储存
    解决json日期格式问题的3种方法(转载)
    Json格式串处理
    全局图片防盗链处理
    我的博客开张了
    iPhone手机屏幕分辨率
    通过CSS3伪类,美化Radio按钮样式
    测试用例 相关
    MongoDB基本命令
  • 原文地址:https://www.cnblogs.com/harryzhr/p/14254788.html
Copyright © 2011-2022 走看看