首页 > 其他分享 >Nityacke的Top Cluster树分块

Nityacke的Top Cluster树分块

时间:2023-10-24 22:14:28浏览次数:40  
标签:cnt int Nityacke ++ st Cluster maxn Top

我们有对序列分块的需求。所以我们有对树分块的需求。

有些出题人喜欢把序列问题放到树上,从而让选手强行写树链剖分。

但是我们想让大家知道,搬到仙人掌上也是可以的。

先给出一些信息:

  • 一个树簇 (cluster) 是树上的一个连通子图,有至多两个点和全树的其他位置连接

  • 这两个节点被称为界点,可以证明,两个界点存在祖孙关系,我们定义深度小的为上界点,另一个为下界点。

  • 不同块的边集不交,一个界点可能在多个簇里面,非界点只可能在一个块里。

  • 两个界点之间的路径被称为簇路径。

  • 如果一个点在多个块里,那一定是一个簇的下界点,同时可能作为很多个簇的上界点。

  • 如果把所有块的端点作为点,每个簇的上界点和下界点连边,则得到一棵树,称为收缩树。

根据这些性质,类似于树剖维护边,我们把每个簇的信息记录在下界点上。

对于根,一般特殊维护,不进行任何关于根的修改、查询。

我们考虑如何构建树分块。

我们从根开始 dfs,我们定义一个栈维护还未被划分到哪个簇的边,在三种情况下需要把边弹出来。

  1. x 为根,为了方便以后的处理,强行钦定 x 为界点。

  2. 当前子树中出现了两个界点,我们必须令这个点为界点来隔开两个簇。

  3. 栈中的边的数量大于了 B。

为什么?1、3 显然。对于 2,我们不希望一个簇有两个下界点,然而子树中的上界点如果作为子树那个簇独立就必须成为当前节点的下界点。但是这不可能。所以我们要合并这两个子树,并以当前的节点作为簇的上界点。

可以证明,这样的簇至多有 \(\dfrac{6n}{B}\) 个。很好!

那么如何利用它呢?这里以常用的链修改、查询为例。

记录块内的簇路径前缀和、收缩树前缀和,每个点到其簇上界点的前缀和。

先把链拆分成祖先-后代链。

修改:对于 \(x\) 所在簇暴力修改,然后对其他块都是簇路径,打标记即可。

查询利用几个前缀和即可。

复杂度是优秀的 \(O(\sqrt n)-O(1)\)。

口胡容易实现代码难。我们来看有详细注释的代码:

这里使用 P4211 的代码。其要求链修链查。但是转化问题不在这里提及,这里着重看树分块。

数组意义:

dep 原树深度,f 原树父亲,Fa 收缩树父亲,sz 子树中未分配点个数,up/down 这个点所属簇的上下界。

uper/downer 这个编号的簇的上下界,stk 是目前(dfs中)的这个簇的元素集合,top 是他的栈顶。

cnt 是簇数量,near 是这个点离簇路径最近的那个点,ind 一个点属于那个块,st 是目前的节点栈,Top 是他的栈顶。

block 是每个有下界的块的下界点,bkcnt 是这个块的计数,S 是每个块的节点 vector。

vis 是临时的每个点是否属于簇路径,val 是每个点到它所属的簇的前缀和,tag 簇路径加的懒标记,sum 是簇路径的前缀和。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=5e4+5,mod=201314,B=400;
int n,m;
vector<int> e[maxn];
int dep[maxn],f[maxn],Fa[maxn],sz[maxn];
int up[maxn],down[maxn],uper[maxn],downer[maxn],stk[maxn],top=0,cnt=0;
int near[maxn],ind[maxn],st[maxn],Top=0,block[maxn],bkcnt=0;
vector<int> S[maxn];
bool vis[maxn];
int val[maxn],sum[maxn],tag[maxn];
inline void add(int x,int y){
	if(y){
		block[++bkcnt]=y,Fa[y]=x;//有下界点就记下下界点
		//这个块的父亲是上界点,因为上界点是父亲簇的下界点
		for(int now=y;now!=x;now=f[now])vis[now]=1;//给簇路径打上标记
	}
	for(int i=1;i<=top;i++){
		int u=stk[i];
		down[u]=y;
		if(stk[i]!=y)up[u]=x;//下界点同时是下一个簇的上界点,上界点记为他自己
		if(!y){near[u]=x;continue;}
		int p=x;//求出near
		for(int now=u;now!=x;now=f[now]){
			if(vis[now]){p=now;break;}
			if(near[now]){p=near[now];break;}
		}
		near[u]=p;
	}
	if(y)for(int now=y;now!=x;now=f[now])vis[now]=0;//恢复为0
}
void dfs(int u,int fa){
	f[u]=fa;
	for(auto it=e[u].begin();it!=e[u].end();it++)//删掉更方便
		if((*it)==fa){e[u].erase(it);break;}
	sz[u]=1;int Cnt=0;up[u]=0;
	for(auto v:e[u]){
		dep[v]=dep[u]+1,st[Top++]=v;
		dfs(v,u);
		if(up[v])++Cnt,up[u]=up[v];//计数儿子中的有界点的数量
		//跟最后一个待在一个簇
		sz[u]+=sz[v];
	}
	int y=0;//下界点
	if(sz[u]>=B||u==1||Cnt>1){//分簇的三个条件
		int Sz=0,ct=0;
		for(int i=(int)e[u].size()-1;i>=0;i--){
			int v=e[u][i];
			Sz+=sz[v];
			if(up[v])++ct;
			if(Sz>B||ct>1){//避免有两个下界点接下去
				Sz=sz[v],top=y=0,++cnt;
				if(up[v])ct=1;
				do{
					stk[++top]=st[--Top];
					y=max(y,up[st[Top]]);//不可言说
					ind[st[Top]]=cnt;
					S[cnt].push_back(st[Top]);// cnt 簇 中插入 st[Top]
				}while(st[Top]!=e[u][i+1]);//不要插到别的子树去了
				add(u,y);//u--y 为簇路径的簇
				uper[cnt]=u,downer[cnt]=y;
			}
		}
		top=y=0,++cnt;//还有一些点没进去
		do{//和上面一样
			stk[++top]=st[--Top];
			y=max(y,up[st[Top]]);
			ind[st[Top]]=cnt;
			S[cnt].push_back(st[Top]);
		}while(st[Top]!=e[u][0]);
		add(u,y);
		uper[cnt]=u,downer[cnt]=y;
		up[u]=u,sz[u]=1;//现在只剩这个点自己了
	}
}
inline void Addval(int u){//1--u ++
	if(u==1)return ;//遇到根直接润
	int tmp=(!Fa[u])?down[u]:0;//是否不是界点
	for(int i=1;i<bkcnt;i++)sum[block[i]]-=sum[Fa[block[i]]];//最后一个是根
	if(tmp)sum[tmp]+=dep[near[u]]-dep[up[u]];//簇路径的贡献
	if(!Fa[u]){
		int j=ind[u];
		for(int i=(int)S[j].size()-1;i>=0;i--){//簇内部按深度排序
			int x=S[j][i];
			if(!Fa[f[x]])val[x]-=val[f[x]];//父亲不是界点:暂时把前缀和变成值
		}
		for(;!Fa[u]&&u>1;u=f[u])val[u]++;//路径暴力加
		for(int i=0;i<S[j].size();i++){
			int x=S[j][i];
			if(!Fa[f[x]])val[x]+=val[f[x]];//再变回来
		}
	}
	for(;u>=1;u=Fa[u])++tag[u],sum[u]+=dep[u]-dep[Fa[u]];//暴力加贡献
	for(int i=bkcnt-1;i>=1;i--)sum[block[i]]+=sum[Fa[block[i]]];
}
inline int getans(int u){//1--u query
	int bel=ind[u];
	int res=val[u]*(downer[bel]!=u)+sum[up[u]];
	//如果这是下界点,就无需统计块内前缀和;因为块间前缀和已经包含了它
	if(!Fa[u]&&down[u])res+=(dep[near[u]]-dep[up[u]])*tag[down[u]];//不是界点就加上tag
    return res;
}

struct query{
	int id,u,z,w;
	query(int a=0,int b=0,int c=0,int d=0){
		id=a,u=b,z=c,w=d;
	}
	bool operator <(const query &b)const{return u<b.u;}
}q[maxn<<1];
bool cmp(int x,int y){
	return dep[x]<dep[y];
}
int ans[maxn];
signed main(){
	//这是离线做法的树分块代替树剖
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=2;i<=n;i++){
		int x;cin>>x;x++;
		e[x].push_back(i);e[i].push_back(x);
	}
	for(int i=1;i<=m;i++){
		int x,y,z;cin>>x>>y>>z;x++,y++,z++;
		q[2*i-1]=query(i,y,z,1);q[2*i]=query(i,x-1,z,-1);
		ans[i]=y-x+1;//根的处理比较特殊
		//这里,我们把所有 dep 减一,贡献额外加上区间长
		//好处就是不用管根的贡献。
	}
	sort(q+1,q+2*m+1);
	dfs(1,0);
	for(int i=1;i<=cnt;i++)sort(S[i].begin(),S[i].end(),cmp);
	int now=0;
	for(int i=1;i<=2*m;i++){
		while(now<q[i].u)Addval(++now);
		(ans[q[i].id]+=mod+q[i].w*getans(q[i].z)%mod)%=mod;
	}
	for(int i=1;i<=m;i++)cout<<ans[i]<<"\n";
	return 0;
}

待补:FLOJ 4031

标签:cnt,int,Nityacke,++,st,Cluster,maxn,Top
From: https://www.cnblogs.com/british-union/p/top-cluster.html

相关文章

  • 面试必刷TOP101:11、链表相加(二)
    一、题目二、题解反转链表:publicListNodeaddInList(ListNodehead1,ListNodehead2){//进行判空处理if(head1==null)returnhead2;if(head2==null){returnhead1;}//反转h1链表head1......
  • Top 19 Docker Commands
    Top19DockerCommands有一天我发现了这个有创造力的社区(bytebytego)和这些有创造力的工程师设计的流程图,很惊喜很喜欢,就把他们留存了下来。......
  • Redis-cluster群集操作步骤(主从切换、新增、删除主从节点)
    1.进入集群客户端任意选一个redis节点,进入redis所在目录cd/redis所在目录/src/./redis-cli-h本地节点的ip-predis的端口号-a密码[root@mysql-db01~]#redis-cli-h10.0.0.51-p637910.0.0.51:6379> 2.查看集群中各个节点状态集群(cluster)clusterinfo......
  • git 图形可视化工具GitHub Desktop 的安装及使用
    直接搜索GitHubDesktop 点进去下载: 下载完根据提示关联自己的github账号克隆一个仓库: 基于某分支新建分支  ......
  • 面试必刷TOP101:9、删除链表的倒数第n个节点
    一、题目二、题解2.1双指针由于我们需要找到倒数第n个节点,因此可以使用两个指针fast和slow同时对链表进行遍历,并且fast比slow超前n个节点。当fast遍历到链表的末尾时,slow就恰好处于倒数第n个节点。具体地,初始时fast和slow均指向头节点。首先使用fast对链表......
  • 面试必刷TOP101:8、链表中倒数最后k个结点
    一、题目输入一个长度为n的链表,设链表中的元素的值为ai ,返回该链表中倒数第k个节点。如果该链表长度小于k,请返回一个长度为0的链表。二、题解2.1快慢指针第一个指针先移动k步,然后第二个指针再从头开始,这个时候这两个指针同时移动,当第一个指针到链表的末尾的时候,返回第二个指......
  • 计时 System.Diagnostics.Stopwatch
     // System.Diagnostics.Stopwatch.StartNew();  //使用StartNew表示已经实例并且开始计时//sw.Reset();//重置//sw.Elapsed.TotalMilliseconds;//毫秒  System.Diagnostics.Stopwatchsw=newSystem.Diagnostics.Stopwatch();  sw.Start();......
  • 面试必刷TOP101:6、判断链表中是否有环
    一、题目二、题解2.1双指针我们使用两个指针,fast与slow。它们起始都位于链表的头部。随后,slow指针每次向后移动一个位置,而fast指针向后移动两个位置。如果链表中存在环,则fast指针最终将再次与slow指针在环中相遇。importjava.util.*;/***Definitionforsingly-linke......
  • 面试必刷TOP101:5、合并k个已排序的链表
    一、题目二、题解顺序合并解题思路1、将k个链表配对并将同一对中的链表进行合并(采用顺序合并的方法)2、第一轮合并后,k个链表合并成了k/2个链表,平均长度2n/k,然后是k/4、k/8...等等3、重复这一过程,知道获取最终的有序链表importjava.util.*;/***Definitionforsingly-linke......
  • Failed to stop auditd.service: Operation refused, unit auditd.service may be req
    [root@7~]#systemctlstopauditd.serviceFailedtostopauditd.service:Operationrefused,unitauditd.servicemayberequestedbydependencyonly(itisconfiguredtorefusemanualstart/stop).Seesystemlogsand'systemctlstatusauditd.service&#......