P3920 【[WC2014]紫荆花之恋】

本人第2题点分树,就搞这题大毒瘤,写了十来个小时吧,几个sb错误捂脸

其实点分树普遍的叫法是动态点分治,但是我还是喜欢叫点分树,原因结尾说

翻了一遍题解,我的代码量是最小的,而且代码还算可读。(纯数组)

人傻常数大,不保证任何时候都跑的过去(写题解的忽然发现是多开了2个longlong......)

你谷的评测波动太恐怖了,我的#8跑2次,一次12.01s,一次6.8s,差了快2倍了


  • 总思路

这题要求的是:

(dis_{u,v}le r_u+r_v)

(dis_{u,lca}+dis_{v,lca}le r_u+r_v)

(r_u-dis_{u,lca}ge dis_{v,lca}-r_v)

如果已经建出了分治树,那么这个可以加上平衡树容斥出来(平衡树维护排名)

虽然直接加叶子不会对重心影响很大,但是加多了就可能被卡,所以考虑类似替罪羊的重构分治树


本篇题解变量声明:

vt,to 分治树上的父亲,父亲指向的子节点(to是个vector)

fa,dep,dist 倍增求2点间距离用,fa是倍增数组,dep是节点深度,dist是距离根的距离

val 题目中的r,感知能力

mx,siz,root,totsize,used ,求子树重心用,mx是最大子树大小,siz是当前子树大小,root是重心,totsize是要求重心的子树的总大小,是否使用过的标记

rub,st,rub_top,st_top ,rub是垃圾箱,st是重构树的时候存节点编号的

num_node 平衡树总结点数

key,sz,ch 平衡树的权值,子树大小,左右儿子

t1,t2 容斥用的2个根节点


  • 平衡树

据说卡常数大的平衡树,于是不敢写我最常写的fhqtreap,写了替罪羊。但是之前没写过替罪羊,纯手推。

说说手推的过程吧:

知道了替罪羊是BST+重构保证复杂度,所以考虑写个BST,插入结束以后判断是否需要重构即可。重构的话就遍历整颗子树,把所有编号存起来,注意中序遍历,因为中序遍历的时候就是有序的,否则重构多个排序,多个log。还需要垃圾回收,和重构的写法差不多吧qwq。但是要么从垃圾箱里拿出来的时候清零元素,要么遍历完立即清零元素,不然会把整颗树变成图的(左右儿子没清零的后果)

然后是空间,我第一次写的时候想不通空间怎么开,过了一天忽然会了:我们插节点的时候是因为它有分治树上的父亲,而这个是log级别的,每个节点一个log,加个垃圾回收空间严格 (nlog n)

但是我们对每个节点开2颗,容斥用,所以空间是 (2 imes 10^5 imes log (10^5)) ,开个4000000比较保险(


  • 统计答案

初学点分树最难理解的地方吧(反正我理解了好久,菜死了)

容斥:对于每个节点x维护2颗平衡树(t1[x],t2[x]),分别维护(vt是x沿分治树到根所有节点) (dis_{vt,x}-val_x)(dis_{vt(vt),x})

注意平衡树插入和查询的时候传的值需要反号,原因见总思路的推导

我们发现,如果只维护一棵平衡树,在跳到上一层分治中心的时候,从当前节点到分治中心的路径会被多算一遍,所以要容斥掉。所以再维护一棵就好了。


  • 重构点分树

    把整个子树拍扁,然后找到重心,当做根,注意维护父子关系,剩下直接普通的建点分树即可

    但是在重构的时候还要重构对应节点的平衡树。这时候可以直接推进垃圾箱,然后像上面一样维护2颗平衡树,重构结束。


  • 插入节点

可以直接往查到点分树上的叶子节点,如果太多不平衡了直接重建。所以一开始完全珂以把它的父亲当做上一层分治中心,等不平衡了就是重构的事了。


  • 注意事项:

    1. 用高速平衡树

    2. 注意重构替罪羊的时候清空元素(我跳过的坑)

    3. 统计答案的时候记得开 long long ,因为最大是 (10^5 imes10^5),其余别开,不然不稳(我跳过的坑)

    4. 如果下数据本地跑,记得开大栈空间,不然会因为重构时dfs的太深RE(我跳过的坑)


代码,还是放出来吧,说不定可以帮一些人。

#include<bits/stdc++.h>
using namespace std;
#define rint register int
typedef long long LL;
inline int rd(){
   int x=0,f=1;
   char ch=getchar();
   while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
   while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
   return x*f;
}
inline int min(const int &a,const int &b) {return a<b?a:b;}
inline int max(const int &a,const int &b) {return a>b?a:b;}
const int N=100010;
const int M=4000010;
const int mod=1000000000;
const double alpha=0.8;
const double beta=0.7;
int n,val[N];
LL ans;
int head[N],num_edge;
int fa[18][N],bin[18],dep[N],dist[N];
int mx[N],siz[N],root,vt[N],totsize;
bool used[N];
int rub[M],rub_top,st[M],st_top,num_node;
int sz[M],ch[M][2],key[M];
int t1[N],t2[N];
vector<int>to[N];
struct egde {
	int to,nxt;
}e[N<<1];
void addedge(int from,int to) {
	++num_edge;
	e[num_edge].nxt=head[from];
	e[num_edge].to=to;
	head[from]=num_edge;
}
int lca(int x,int y) {
	if(dep[x]<dep[y])x^=y^=x^=y;
	int delta=dep[x]-dep[y];
	for(rint i=17;i>=0;--i)if(delta&bin[i])x=fa[i][x];
	if(x==y)return x;
	for(rint i=17;i>=0;--i)if(fa[i][x]!=fa[i][y])x=fa[i][x],y=fa[i][y];
	return fa[0][x];
}
LL dis(int x,int y) {return dist[x]+dist[y]-(dist[lca(x,y)]<<1);}
void push_in_rub(int u) {
	if(!u)return;
	push_in_rub(ch[u][0]);
	rub[++rub_top]=u;
	push_in_rub(ch[u][1]);
	ch[u][0]=ch[u][1]=sz[u]=key[u]=0;
}
void pushup(int u) {
	sz[u]=sz[ch[u][0]]+sz[ch[u][1]]+1;
}
void push_in_st(int u) {
	if(!u)return;
	push_in_st(ch[u][0]);
	st[++st_top]=u;
	push_in_st(ch[u][1]);
	ch[u][0]=ch[u][1]=0;
}
int getnew() {
	return rub_top?rub[rub_top--]:++num_node;
}
void build(int &x,int l,int r) {
	if(l>r)return;
	int mid=(l+r)>>1;
	x=st[mid];sz[x]=r-l+1;
	build(ch[x][0],l,mid-1);
	build(ch[x][1],mid+1,r);
}
void rebuild(int &u) {
	st_top=0;push_in_st(u);
	build(u,1,st_top);
}
void insert(int &u,int val) {
	if(!u) {u=getnew(),key[u]=val,sz[u]=1;return;}
	if(val<=key[u])insert(ch[u][0],val);
	else insert(ch[u][1],val);
	pushup(u);
	if(sz[ch[u][0]]>1.0*sz[u]*beta)rebuild(u);
	if(sz[ch[u][1]]>1.0*sz[u]*beta)rebuild(u);
}
int rk(int u,int val) {
	int res=0,now=u;
	while(now) {
		if(key[now]<val)res+=sz[ch[now][0]]+1,now=ch[now][1];
		else now=ch[now][0];
	}
	return res;
}
void getroot(int u,int ft) {
	mx[u]=0,siz[u]=1;
	for(rint i=head[u];i;i=e[i].nxt) {
		int v=e[i].to;
		if(v==ft||used[v])continue;
		getroot(v,u);
		siz[u]+=siz[v];
		mx[u]=max(mx[u],siz[v]);
	}
	mx[u]=max(mx[u],totsize-siz[u]);
	if(mx[root]>mx[u])root=u;
}
void calc(int u,int ft,int Vt) {
	insert(t1[Vt],dis(u,Vt)-val[u]);
	if(vt[Vt])insert(t2[Vt],dis(u,vt[Vt])-val[u]);
	for(rint i=head[u];i;i=e[i].nxt) {
		int v=e[i].to;
		if(v==ft||used[v])continue;
		calc(v,u,Vt);
	}
}
void dfs_build(int u) {
	calc(u,0,u);used[u]=1;
	for(rint i=head[u];i;i=e[i].nxt) {
		int v=e[i].to;
		if(used[v])continue;
		totsize=siz[v];
		root=0;
		getroot(v,0);
		vt[root]=u;
		to[u].push_back(root);
		dfs_build(root);
	}
}
void dfs_pia(int u) {
	++totsize;used[u]=0;
	push_in_rub(t1[u]);
	push_in_rub(t2[u]);
	t1[u]=t2[u]=0;
	for(rint i=0;i<to[u].size();++i)
		dfs_pia(to[u][i]);
	to[u].clear();
}
void dfs_rebuild(int u) {
	totsize=0;dfs_pia(u);root=0;getroot(u,0);
	if(vt[u])
		for(rint i=0;i<to[vt[u]].size();++i)
			if(to[vt[u]][i]==u)to[vt[u]][i]=root;
	vt[root]=vt[u];dfs_build(root);
}
void dfs_insert(int x) {
	for(rint i=x;vt[i];i=vt[i]) {
		int tmp=val[x]-dis(x,vt[i])+1;
		ans+=rk(t1[vt[i]],tmp)-rk(t2[i],tmp);
	}
	for(rint i=x;i;i=vt[i]) {
		insert(t1[i],dis(i,x)-val[x]);
		if(vt[i])insert(t2[i],dis(x,vt[i])-val[x]);
	}
	int res=0;
	for(rint i=x;vt[i];i=vt[i])
		if(sz[t1[i]]>1.0*sz[t1[vt[i]]]*alpha)res=vt[i];
	if(res)dfs_rebuild(res);
}
signed main() {
	rd(),n=rd();
	bin[0]=1;for(rint i=1;i<=17;++i)bin[i]=bin[i-1]<<1;
	mx[root=0]=n;
	for(rint i=1;i<=n;++i) {
		vt[i]=fa[0][i]=(rd()^(ans%mod));
		int c=rd();val[i]=rd();used[i]=1;
		dep[i]=dep[fa[0][i]]+1;
		dist[i]=dist[fa[0][i]]+c;
		for(rint j=1;j<=17;++j)fa[j][i]=fa[j-1][fa[j-1][i]];
		if(vt[i])to[vt[i]].push_back(i),addedge(i,fa[0][i]),addedge(fa[0][i],i);
		dfs_insert(i);printf("%lld
",ans);
	}
	return 0;
} 

之前说我喜欢叫点分树,因为如果您和一个刚学不久的OIer讨论,很可能出现如下坑人的对话

A:您会dfs吗?

B:会啊,刚学

A:您学了几个月就会点分树了,吊打我%%%

B:?

其实一点都不好玩

打代码去吧qwq,希望我这篇题解对大家有帮助,希望大家早点AC了这题!

路漫漫其修远兮,吾将上下而求索
原文地址:https://www.cnblogs.com/zzctommy/p/12918520.html