bzoj3435 [Wc2014]紫荆花之恋(动态点分治+替罪羊树)

时间:2023-03-09 18:22:22
bzoj3435 [Wc2014]紫荆花之恋(动态点分治+替罪羊树)

传送门(权限)

传送门(非权限)

题解

  我终终终终终终于做出来啦!!!

  作为一个没有学过替罪羊树的蒟蒻现场学了一下替罪羊树,作为一个平衡树都写数组版本的看着大佬的指针题解无语只能硬去理解然后照着抄了一波指针

  然后怎么做呢?

  先把题设式子变形一下$$dist(i,j)\leq r_i+r_j$$

  $$dist(i,LCA)+dist(LCA,j)\leq r_i+r_j$$

  $$r_i-dist(i,LCA)\geq dist(j,LCA)-r_j$$

  然后我们在每一个点开两棵平衡树,分别维护以$i$为根的子树中$dist(i,u)-r_u$和$dist(fa[i],u)-r_u$的值。然后每一次跳点分树时,记录$r_i-dist(i,LCA)+1$,在平衡树里查询有多少个数小于它就好了,修改直接往上跳,不断改

  然而如果原树是一条链怎么办?强制在线,必然会被卡成$O(n^2)$,怎么办?

  我们联想一下替罪羊树的思想,如果点分树上某一个点的子树过大,直接拍扁重建。联想一下替罪羊树,可以发现时间复杂度是能得到保证的,这样可以保证时间复杂度是$O(nlogn)$。

  因为蒟蒻是第一次写替罪羊树&&第一次码这么长的代码,于是加了一堆注释,米娜应该能够看懂吧……

 // luogu-judger-enable-o2
//minamoto
#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
#define ll long long
#define inf 1000000000
#define N 100005
#define alpha 0.755
using namespace std;
#define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[<<],*p1=buf,*p2=buf;
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,:;}
inline int read(){
#define num ch-'0'
char ch;bool flag=;int res;
while(!isdigit(ch=getc()))
(ch=='-')&&(flag=true);
for(res=num;isdigit(ch=getc());res=res*+num);
(flag)&&(res=-res);
#undef num
return res;
}
char sr[<<],z[];int C=-,Z;
inline void Ot(){fwrite(sr,,C+,stdout),C=-;}
inline void print(ll x){
if(C><<)Ot();if(x<)sr[++C]=,x=-x;
while(z[++Z]=x%+,x/=);
while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
int n,e,head[N],Next[N<<],ver[N<<],val[N];
inline void add(int u,int v){
//加边,构建原树
ver[++e]=v,Next[e]=head[u],head[u]=e;
ver[++e]=u,Next[e]=head[v],head[v]=e;
}
vector<int> to[N];
int f[N][],bin[],tp,dep[N],len[N];
inline int LCA(int a,int b){
if(dep[a]<dep[b]) a^=b^=a^=b;
int i,cha=dep[a]-dep[b];
for(i=tp;~i;--i) if(cha&bin[i]) a=f[a][i];
if(a==b) return a;
for(i=tp;~i;--i) if(f[a][i]!=f[b][i]) a=f[a][i],b=f[b][i];
return f[a][];
}
inline int dis(int a,int b){return len[a]+len[b]-(len[LCA(a,b)]<<);}
struct Goat{
int val,sz;Goat *ch[];
Goat(){}
inline bool bad(){
//判断是否某个子树过大
return ch[]->sz>=sz*alpha+||ch[]->sz>=sz*alpha+;
}
}*tree1[N],*tree2[N],mem[N<<],*pool[N<<],*null,*sta[N];
int tot,top;
void init(){
//构建内存池,避免动态开点时间复杂度太大
null=new Goat();
null->ch[]=null->ch[]=null,null->val=null->sz=;
for(int i=;i<(N<<);++i) pool[i]=mem+i;
tot=(N<<)-;
for(int i=;i<=n;++i) tree1[i]=tree2[i]=null;
}
Goat** insert(Goat *&a,int val){
//插入节点,并判断是否有子树过大
//注意要开引用
if(a==null){
a=pool[tot--],a->ch[]=a->ch[]=null;
a->val=val,a->sz=;return &null;
}
++a->sz;
//小于等于往左插,大于往右插
Goat **o=insert(a->ch[a->val<val],val);
if(a->bad()) o=&a;return o;
}
int getrk(Goat *o,int val){
//查找有多少比val小的数
if(o==null) return ;
return (o->val>=val)?getrk(o->ch[],val):(getrk(o->ch[],val)+o->ch[]->sz+);
}
void Erholung(Goat *o){
//清除节点,回收内存池
if(o==null) return;
if(o->ch[]!=null) Erholung(o->ch[]);
pool[++tot]=o;
if(o->ch[]!=null) Erholung(o->ch[]);
}
void travel(Goat *o){
//暴力重构整棵树(递归找节点)
if(o==null) return;
if(o->ch[]!=null) travel(o->ch[]);
sta[++top]=o;
if(o->ch[]!=null) travel(o->ch[]);
}
Goat* build(int l,int r){
//重构
if(l>r) return null;
int mid=l+r>>;
Goat *o=sta[mid];o->sz=r-l+;
o->ch[]=build(l,mid-),o->ch[]=build(mid+,r);
return o;
}
inline void rebuild(Goat *&o){top=,travel(o),o=build(,top);};
inline void Insert(Goat *&a,int val){
//同,这里和上面rebuild也要开引用
Goat **o=insert(a,val);
if(*o!=null) rebuild(*o);
}
int sz[N],son[N],size,rt,fa[N];bool vis[N];
void findrt(int u,int fa){
sz[u]=,son[u]=;
for(int i=head[u];i;i=Next[i]){
int v=ver[i];
if(v!=fa&&!vis[v]){
findrt(v,u),sz[u]+=sz[v],cmax(son[u],sz[v]);
}
}
cmax(son[u],size-sz[u]);
if(son[u]<son[rt]) rt=u;
}
void dfs(int u,int f,int rt){
//遍历子树,把所有的东西都插到平衡树里
Insert(tree1[rt],dis(u,rt)-val[u]);
if(fa[rt]) Insert(tree2[rt],dis(u,fa[rt])-val[u]);
for(int i=head[u];i;i=Next[i]){
int v=ver[i];
if(v!=f&&!vis[v]) dfs(v,u,rt);
}
}
void solve(int u,int f){
fa[u]=f,vis[u]=;
int totsz=size;
dfs(u,,u);
for(int i=head[u];i;i=Next[i]){
int v=ver[i];
if(!vis[v]){
rt=,size=sz[v]>sz[u]?totsz-sz[u]:sz[v];
findrt(v,),to[u].push_back(rt),solve(rt,u);
}
}
}
void recover(int x){
//遍历点分树,清空节点
++size,vis[x]=;
Erholung(tree1[x]),Erholung(tree2[x]);
tree1[x]=tree2[x]=null;
for(int i=,k=to[x].size();i<k;++i) recover(to[x][i]);
to[x].clear();
}
void rebuild(int x){
//点分树某一子树过大,重构
size=,recover(x),rt=,findrt(x,);
if(fa[x])
for(int i=,j=to[fa[x]].size();i<j;++i)
if(to[fa[x]][i]==x) to[fa[x]][i]=rt;
solve(rt,fa[x]);
}
ll ans=;
int insert(int x){
register int i,ds,res=;
//求出小于等于val[x]-dis(x,fa[i])的个数,只要在平衡树里找小于val[x]-dis(x,fa[i])+1的就可以了
for(i=x;fa[i];i=fa[i])
ds=val[x]-dis(x,fa[i])+,ans+=getrk(tree1[fa[i]],ds)-getrk(tree2[i],ds);
Insert(tree1[x],-val[x]);
//然后维护修改
for(i=x;fa[i];i=fa[i]){
int dist=dis(fa[i],x)-val[x];
Insert(tree1[fa[i]],dist);
Insert(tree2[i],dist);
}
//考虑是否需要拍扁重建
for(i=x;fa[i];i=fa[i])
if(tree1[i]->sz>=tree1[fa[i]]->sz*alpha+) res=fa[i];
return res;
}
int main(){
n=read(),n=read();
register int i,j,b,x;
for(bin[]=i=;i<=;++i) bin[i]=bin[i-]<<;
while(bin[tp+]<=n) ++tp;
son[]=n+,rt=,init();
for(int i=;i<=n;++i){
fa[i]=f[i][]=read()^(ans%inf),b=read(),val[i]=read();
dep[i]=dep[f[i][]]+,len[i]=len[f[i][]]+b,vis[i]=;
if(fa[i]) to[fa[i]].push_back(i),add(f[i][],i);
for(j=;bin[j]+<=dep[i];++j) f[i][j]=f[f[i][j-]][j-];
x=insert(i);if(x) rebuild(x);
print(ans);
}
Ot();
return ;
}