本人第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颗平衡树,重构结束。
- 插入节点
可以直接往查到点分树上的叶子节点,如果太多不平衡了直接重建。所以一开始完全珂以把它的父亲当做上一层分治中心,等不平衡了就是重构的事了。
-
注意事项:
-
用高速平衡树
-
注意重构替罪羊的时候清空元素(我跳过的坑)
-
统计答案的时候记得开
long long
,因为最大是 (10^5 imes10^5),其余别开,不然不稳(我跳过的坑) -
如果下数据本地跑,记得开大栈空间,不然会因为重构时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了这题!