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)$。
因为蒟蒻是第一次写替罪羊树&&第一次码这么长的代码,于是加了一堆注释,米娜应该能够看懂吧……
1 // luogu-judger-enable-o2 2 //minamoto 3 #include<cstdio> 4 #include<cstring> 5 #include<iostream> 6 #include<vector> 7 #define ll long long 8 #define inf 1000000000 9 #define N 100005 10 #define alpha 0.755 11 using namespace std; 12 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) 13 char buf[1<<21],*p1=buf,*p2=buf; 14 template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;} 15 inline int read(){ 16 #define num ch-'0' 17 char ch;bool flag=0;int res; 18 while(!isdigit(ch=getc())) 19 (ch=='-')&&(flag=true); 20 for(res=num;isdigit(ch=getc());res=res*10+num); 21 (flag)&&(res=-res); 22 #undef num 23 return res; 24 } 25 char sr[1<<21],z[20];int C=-1,Z; 26 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;} 27 inline void print(ll x){ 28 if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x; 29 while(z[++Z]=x%10+48,x/=10); 30 while(sr[++C]=z[Z],--Z);sr[++C]='\n'; 31 } 32 int n,e,head[N],Next[N<<1],ver[N<<1],val[N]; 33 inline void add(int u,int v){ 34 //加边,构建原树 35 ver[++e]=v,Next[e]=head[u],head[u]=e; 36 ver[++e]=u,Next[e]=head[v],head[v]=e; 37 } 38 vector<int> to[N]; 39 int f[N][18],bin[25],tp,dep[N],len[N]; 40 inline int LCA(int a,int b){ 41 if(dep[a]<dep[b]) a^=b^=a^=b; 42 int i,cha=dep[a]-dep[b]; 43 for(i=tp;~i;--i) if(cha&bin[i]) a=f[a][i]; 44 if(a==b) return a; 45 for(i=tp;~i;--i) if(f[a][i]!=f[b][i]) a=f[a][i],b=f[b][i]; 46 return f[a][0]; 47 } 48 inline int dis(int a,int b){return len[a]+len[b]-(len[LCA(a,b)]<<1);} 49 struct Goat{ 50 int val,sz;Goat *ch[2]; 51 Goat(){} 52 inline bool bad(){ 53 //判断是否某个子树过大 54 return ch[0]->sz>=sz*alpha+5||ch[1]->sz>=sz*alpha+5; 55 } 56 }*tree1[N],*tree2[N],mem[N<<8],*pool[N<<8],*null,*sta[N]; 57 int tot,top; 58 void init(){ 59 //构建内存池,避免动态开点时间复杂度太大 60 null=new Goat(); 61 null->ch[0]=null->ch[1]=null,null->val=null->sz=0; 62 for(int i=0;i<(N<<8);++i) pool[i]=mem+i; 63 tot=(N<<8)-1; 64 for(int i=0;i<=n;++i) tree1[i]=tree2[i]=null; 65 } 66 Goat** insert(Goat *&a,int val){ 67 //插入节点,并判断是否有子树过大 68 //注意要开引用 69 if(a==null){ 70 a=pool[tot--],a->ch[0]=a->ch[1]=null; 71 a->val=val,a->sz=1;return &null; 72 } 73 ++a->sz; 74 //小于等于往左插,大于往右插 75 Goat **o=insert(a->ch[a->val<val],val); 76 if(a->bad()) o=&a;return o; 77 } 78 int getrk(Goat *o,int val){ 79 //查找有多少比val小的数 80 if(o==null) return 0; 81 return (o->val>=val)?getrk(o->ch[0],val):(getrk(o->ch[1],val)+o->ch[0]->sz+1); 82 } 83 void Erholung(Goat *o){ 84 //清除节点,回收内存池 85 if(o==null) return; 86 if(o->ch[0]!=null) Erholung(o->ch[0]); 87 pool[++tot]=o; 88 if(o->ch[1]!=null) Erholung(o->ch[1]); 89 } 90 void travel(Goat *o){ 91 //暴力重构整棵树(递归找节点) 92 if(o==null) return; 93 if(o->ch[0]!=null) travel(o->ch[0]); 94 sta[++top]=o; 95 if(o->ch[1]!=null) travel(o->ch[1]); 96 } 97 Goat* build(int l,int r){ 98 //重构 99 if(l>r) return null; 100 int mid=l+r>>1; 101 Goat *o=sta[mid];o->sz=r-l+1; 102 o->ch[0]=build(l,mid-1),o->ch[1]=build(mid+1,r); 103 return o; 104 } 105 inline void rebuild(Goat *&o){top=0,travel(o),o=build(1,top);}; 106 inline void Insert(Goat *&a,int val){ 107 //同,这里和上面rebuild也要开引用 108 Goat **o=insert(a,val); 109 if(*o!=null) rebuild(*o); 110 } 111 int sz[N],son[N],size,rt,fa[N];bool vis[N]; 112 void findrt(int u,int fa){ 113 sz[u]=1,son[u]=0; 114 for(int i=head[u];i;i=Next[i]){ 115 int v=ver[i]; 116 if(v!=fa&&!vis[v]){ 117 findrt(v,u),sz[u]+=sz[v],cmax(son[u],sz[v]); 118 } 119 } 120 cmax(son[u],size-sz[u]); 121 if(son[u]<son[rt]) rt=u; 122 } 123 void dfs(int u,int f,int rt){ 124 //遍历子树,把所有的东西都插到平衡树里 125 Insert(tree1[rt],dis(u,rt)-val[u]); 126 if(fa[rt]) Insert(tree2[rt],dis(u,fa[rt])-val[u]); 127 for(int i=head[u];i;i=Next[i]){ 128 int v=ver[i]; 129 if(v!=f&&!vis[v]) dfs(v,u,rt); 130 } 131 } 132 void solve(int u,int f){ 133 fa[u]=f,vis[u]=1; 134 int totsz=size; 135 dfs(u,0,u); 136 for(int i=head[u];i;i=Next[i]){ 137 int v=ver[i]; 138 if(!vis[v]){ 139 rt=0,size=sz[v]>sz[u]?totsz-sz[u]:sz[v]; 140 findrt(v,0),to[u].push_back(rt),solve(rt,u); 141 } 142 } 143 } 144 void recover(int x){ 145 //遍历点分树,清空节点 146 ++size,vis[x]=0; 147 Erholung(tree1[x]),Erholung(tree2[x]); 148 tree1[x]=tree2[x]=null; 149 for(int i=0,k=to[x].size();i<k;++i) recover(to[x][i]); 150 to[x].clear(); 151 } 152 void rebuild(int x){ 153 //点分树某一子树过大,重构 154 size=0,recover(x),rt=0,findrt(x,0); 155 if(fa[x]) 156 for(int i=0,j=to[fa[x]].size();i<j;++i) 157 if(to[fa[x]][i]==x) to[fa[x]][i]=rt; 158 solve(rt,fa[x]); 159 } 160 ll ans=0; 161 int insert(int x){ 162 register int i,ds,res=0; 163 //求出小于等于val[x]-dis(x,fa[i])的个数,只要在平衡树里找小于val[x]-dis(x,fa[i])+1的就可以了 164 for(i=x;fa[i];i=fa[i]) 165 ds=val[x]-dis(x,fa[i])+1,ans+=getrk(tree1[fa[i]],ds)-getrk(tree2[i],ds); 166 Insert(tree1[x],-val[x]); 167 //然后维护修改 168 for(i=x;fa[i];i=fa[i]){ 169 int dist=dis(fa[i],x)-val[x]; 170 Insert(tree1[fa[i]],dist); 171 Insert(tree2[i],dist); 172 } 173 //考虑是否需要拍扁重建 174 for(i=x;fa[i];i=fa[i]) 175 if(tree1[i]->sz>=tree1[fa[i]]->sz*alpha+5) res=fa[i]; 176 return res; 177 } 178 int main(){ 179 n=read(),n=read(); 180 register int i,j,b,x; 181 for(bin[0]=i=1;i<=20;++i) bin[i]=bin[i-1]<<1; 182 while(bin[tp+1]<=n) ++tp; 183 son[0]=n+1,rt=0,init(); 184 for(int i=1;i<=n;++i){ 185 fa[i]=f[i][0]=read()^(ans%inf),b=read(),val[i]=read(); 186 dep[i]=dep[f[i][0]]+1,len[i]=len[f[i][0]]+b,vis[i]=1; 187 if(fa[i]) to[fa[i]].push_back(i),add(f[i][0],i); 188 for(j=1;bin[j]+1<=dep[i];++j) f[i][j]=f[f[i][j-1]][j-1]; 189 x=insert(i);if(x) rebuild(x); 190 print(ans); 191 } 192 Ot(); 193 return 0; 194 }
深深地明白自己的弱小