UOJ 435 - Simple Tree

话说这货不去切eJOI在这干啥子呢(

UOJ题目页面传送门

有一棵大小为\(n\)的树,根为\(1\),节点\(i\)有一个权值\(a_i\)。支持\(3\)\(q\)次操作:

  1. \(\texttt1\ x\ y\ v\):令所有在路径\(x\to y\)上的点的权值增加\(v\),保证\(v=\pm1\)
  2. \(\texttt2\ x\ y\):求路径\(x\to y\)上权值\(>0\)的点数;
  3. \(\texttt3\ x\):求子树\(x\)内权值\(>0\)的点数。

强制在线。

\(n,q\in\left[1,10^5\right],a_i\in\left[-10^9,10^9\right]\)

一眼重剖。于是转化为线性结构上的区间修改和区间查询。然后就不会了

看到区间查询排名,想到线段树套平衡树,但是这是区间修改,歇的了。

于是,分块是无所不能的(

注意到一个性质,由于\(v=\pm1\),所以若\(a_i\leq-q\)\(a_i\geq q+1\),则\([a_i>0]\)永远无法改变。所以不妨等效地将\(<-q\)的赋成\(-q\),将\(>q+1\)的赋成\(q+1\),此时值域\(\mathrm O(q)\)

考虑将线性结构\(a\)分成\(sz1\)块,每块\(i\)内维护后缀计数\(cnT_i\)\(cnT_{i,j}\)表示块\(i\)\(\geq j\)的数的个数。再维护一个整体增加标记\(add_i\),表示该块被整体增加过多少。

  • 区间修改:对于两边不满的块,暴力修改,每个数\(\pm1\)的话\(cnT\)是可以\(\mathrm O(1)\)更新的(当然你重构我也不拦你)。对于中间的整块们,直接修改它们的整体增加标记。\(\mathrm O\!\left(sz1+\dfrac n{sz1}\right)\)

  • 区间查询:对于两边不满的块,暴力计数,注意每个数真正的值是它在\(a\)数组中的值加上所在块的整体增加标记。对于中间的整块\(i\)们,调用后缀计数,将\(cnT_{i,1-add_i}\)累加进结果即可。最终答案为两种结果加起来。\(\mathrm O\!\left(sz1+\dfrac n{sz1}\right)\)

此时令\(sz1=\left\lfloor\sqrt n\right\rfloor\),加上重剖的\(\log\)即可\(\mathrm O\!\left(q\sqrt n\log n\right)\)完美滚粗。当然往死里卡还是能卡过去的,我才不会告诉你我曾经就卡过去了呢(

接下来用出题人智商分析法。如果仅限于此,那么这题不就成了强行上树了?怎么还能成为集训队作业2018呢?所以这个重剖肯定有深藏不露之处。

事实上:无论是对于区间修改还是区间查询,对整块处理的时间复杂度显然是整块的个数\(\mathrm O\!\left(\dfrac n{sz1}\right)\)。一次分块维护\(\mathrm O\!\left(\dfrac n{sz1}\right)\),那么一条链是不是就需要\(\mathrm O\!\left(\dfrac n{sz1}\log n\right)\)了呢?不,不是。因为重剖出来的区间们不会有交集,那么总的整块的个数依然是\(\mathrm O\!\left(\dfrac n{sz1}\right)\)级别的。于是一条链的复杂度就是\(\mathrm O\!\left(sz1\cdot\log n+\dfrac n{sz1}\right)\)。注意到两项乘积依然为常数,令\(sz1\cdot\log n=\dfrac n{sz1}\)解得\(sz1=\sqrt{\dfrac n{\log n}}\)(令\(sz1=\left\lfloor\sqrt{\dfrac n{\log_2n}}\right\rfloor\)),此时总时间复杂度为\(\mathrm O\!\left(q\sqrt{n\log n}\right)\)

然鹅这样空间复杂度为\(\mathrm O\!\left(q\sqrt{n\log n}\right)\),虽然ML很大但还是超过了一倍。注意到\(cnT\)的值们不会很大,用short存可以说完美,不多不少刚刚好。

常数还是有点大,不过开个O3就AC了。

代码:

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=100000,QU=100000,DB_SZ=1300;
int n,qu;
bool ol;
vector<int> nei[N+1]; 
int fa[N+1],sz[N+1],wson[N+1],dep[N+1],top[N+1],dfn[N+1],nowdfn,mxdfn[N+1];
void dfs1(int x=1){//重剖预处理,下同 
	sz[x]=1;
	for(int i=0;i<nei[x].size();i++){
		int y=nei[x][i];
		if(y==fa[x])continue;
		fa[y]=x;
		dep[y]=dep[x]+1;
		dfs1(y);
		sz[x]+=sz[y];
		if(sz[y]>sz[wson[x]])wson[x]=y;
	}
}
void dfs2(int x=1,int t=1){
	dfn[x]=mxdfn[x]=++nowdfn;
	top[x]=t;
	if(wson[x])dfs2(wson[x],t),mxdfn[x]=mxdfn[wson[x]];
	for(int i=0;i<nei[x].size();i++){
		int y=nei[x][i];
		if(y==fa[x]||y==wson[x])continue;
		dfs2(y,y);mxdfn[x]=mxdfn[y];
	}
}
int a[N+1];
struct dvdblk{//分块 
	int sz,sz1;
	struct block{int l,r,add;short cnT[2*QU+2];}blk[DB_SZ];
	#define l(p) blk[p].l
	#define r(p) blk[p].r
	#define cnT(p) blk[p].cnT
	#define add(p) blk[p].add
	void bldblk(int p,int l,int r){//构造一个块 
		l(p)=l;r(p)=r;
		add(p)=0;
		for(int i=l;i<=r;i++)cnT(p)[a[i]+qu]++;
		for(int i=2*qu;~i;i--)cnT(p)[i]+=cnT(p)[i+1];
	}
	void init(){//分块初始化 
		sz1=max(1,min(n,int(sqrt(n/max(1.,log2(n))))));
//		printf("sz1=%d\n",sz1);
		sz=(n+sz1-1)/sz1;
		for(int i=1;i<=sz;i++)bldblk(i,(i-1)*sz1+1,min(n,i*sz1));
	}
	void _add(int l,int r,int v){//区间修改 
		int pl=(l+sz1-1)/sz1,pr=(r+sz1-1)/sz1;
		if(pl==pr){//不满的块 
			for(int i=l;i<=r;i++)
				if(v==-1){if(a[i]>-qu)cnT(pl)[a[i]-- +qu]--;}
				else{if(a[i]<qu+1)cnT(pl)[++a[i]+qu]++;}
			return;
		}
		//整块 
		for(int i=pl+1;i<pr;i++)add(i)+=v;
		_add(l,r(pl),v);_add(l(pr),r,v);
	}
	int grt0(int l,int r){//区间查询 
		int pl=(l+sz1-1)/sz1,pr=(r+sz1-1)/sz1;
		if(pl==pr){//不满的块 
			int res=0;
			for(int i=l;i<=r;i++)res+=a[i]+add(pl)>0;
//			cout<<l<<" "<<r<<":"<<res<<"\n";
			return res;
		}
		//整块 
		int res=0;
		for(int i=pl+1;i<pr;i++)res+=cnT(i)[max(-qu,min(qu+1,1-add(i)))+qu];
		return res+grt0(l,r(pl))+grt0(l(pr),r);
	}
}db;
void add_chn(int x,int y,int v){//链修改 
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		db._add(dfn[top[x]],dfn[x],v);
		x=fa[top[x]];
	}
	if(dep[x]<dep[y])swap(x,y);
	db._add(dfn[y],dfn[x],v);
}
int grt0_chn(int x,int y){//链查询 
	int res=0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		res+=db.grt0(dfn[top[x]],dfn[x]);
		x=fa[top[x]];
	}
	if(dep[x]<dep[y])swap(x,y);
	return res+db.grt0(dfn[y],dfn[x]);
}
int grt0_subt(int x){return db.grt0(dfn[x],mxdfn[x]);}//子树查询 
int main(){
//	cout<<sizeof(db)/1024/1024;
	cin>>n>>qu>>ol;
	for(int i=1;i<n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		nei[x].pb(y);nei[y].pb(x);
	}
	dfs1();dfs2();//重剖 
	for(int i=1;i<=n;i++){
		scanf("%d",a+dfn[i]);
		a[dfn[i]]=max(-qu,min(qu+1,a[dfn[i]]));//限制值域 
	}
	db.init();//分块初始化 
	int lasans=0;
	for(int i=1;i<=qu;i++){
		int tp,x,y,z;
		scanf("%d%d",&tp,&x);ol&&(x^=lasans);
		if(tp==1)scanf("%d%d",&y,&z),ol&&(y^=lasans),add_chn(x,y,z);
		else if(tp==2)scanf("%d",&y),ol&&(y^=lasans),printf("%d\n",lasans=grt0_chn(x,y));
		else printf("%d\n",lasans=grt0_subt(x));
	}
	return 0;
}
posted @ 2020-06-06 16:40  ycx060617  阅读(660)  评论(3编辑  收藏  举报