理论

我们需要一个数据结构维护树上的问题,仿照序列上的问题,我们需要一个方法快速的刻画出信息。

比如说线段树就通过分治的方式来通过将一个区间划分成 \(\log n\) 个区间并刻画出这 \(\log n\) 个区间的信息。

然后我们考虑把这个东西放到树上类比。你发现线段树上每个非叶节点都有两个儿子,那么你划分树上信息的方式应当也满足这个性质,也就是说把树划分成联通子图的过程中,应当每次合并两个联通子图,同时线段树只有两个端点(维护的是区间)所以这个联通子图应当只有两个界点(与其他联通子图公用的点,这里认为维护的是边集信息,联通子图间边集不交)。

接下来我们把一个划分出的联通子图称为簇。初始每条边都是一个簇。在合并簇的过程中合并出新的簇也会以一条边的形式出现在树上用于下一次合并。

合并两个簇

分为两种。

compress

我们将一个二度点缩掉,或者说将对于两条相邻的边,若他们的公共点度数为 \(2\) 那么就把这两条边合并。合并出的新簇包含原来两个簇的边集,我们记这样的点是 C 点。

rake

对于一个度数为 \(1\) 的点 \(u\),其唯一的边为 \((u,v)\) 我们将 \((u,v)\) 这条边代表的簇与点 \(v\) 任意一个其他的边代表的簇合并。

最后不难发现的是只会剩下一条边。而合并的过程建出一棵树就是 Top tree。

静态构建

实际上根据全局平衡二叉树的建法可以给出一个 \(2 \times \log n\) 树高的构建方法。

首先按全局平衡二叉树的方法对原树划分,然后轻儿子先把所有簇收缩后 rake 到虚父亲上,对于一条轻儿子全部 rake 完成的重链再用全局平衡二叉树对重链的划分方式把重链上所有边 compress 成一条然后向上递归。

不过对于有多个轻儿子的点显然是有问题的。所以对于多个轻儿子在按照重量选取带权中点,每次按照中点分治,两个分治区间内的轻儿子 rake 成一条在 rake 到一起,还是重量平衡的,所以树高 \(2 \times \log n\)。

按照这种方法建树,需要 \(2\) 倍空间。

维护点信息

对于每个簇我们维护的信息不包括界点信息,在合并信息的时候再把删除的界点的信息带上,那么你发现修改点只需要在他被删掉的簇时修改在一路爬上去就行了,时间复杂度是 \(O(\log n)\)。

应用

动态 dp

维护矩阵(线性变换)

ABC351G

对于一个联通子图来说,若其中只有一个叶子的值不确定,我们可以把这个联通子图的根的 dp 值写成关于不确定叶子的值的线性变换形式,对于这道题目而言就是 \(y = k \times x + b\) 的一次函数形式。

更进一步地化简,因为 \(dp_{u} = a_{u} + \prod dp_v\),而 \(a_u\) 是一个很简单的项,所以不妨对于每个联通子图表示出 \(\prod dp_v = k \times dp_{x} + b\) 这里 \(x\) 表示联通子图中尚未确定的那个点的 \(dp\) 值。

#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
const int mod = 998244353;
//#define lowbit(x) (x&(-x))
using namespace std;
namespace IO{
const int SIZE=1<<21;
static char ibuf[SIZE],obuf[SIZE],*iS,*iT,*oS=obuf,*oT=oS+SIZE-1;
int qr;
char qu[55],c;
bool f;
#define getchar() (IO::iS==IO::iT?(IO::iT=(IO::iS=IO::ibuf)+fread(IO::ibuf,1,IO::SIZE,stdin),(IO::iS==IO::iT?EOF:*IO::iS++)):*IO::iS++)
#define putchar(x) *IO::oS++=x,IO::oS==IO::oT?flush():0
#define flush() fwrite(IO::obuf,1,IO::oS-IO::obuf,stdout),IO::oS=IO::obuf
#define puts(x) IO::Puts(x)
template<typename T>
inline void read(T&x){
for(f=1,c=getchar();c<48||c>57;c=getchar())f^=c=='-';
for(x=0;c<=57&&c>=48;c=getchar()) x=(x<<1)+(x<<3)+(c&15);
x=f?x:-x;
}
template<typename T>
inline void write(T x){
if(!x) putchar(48); if(x<0) putchar('-'),x=-x;
while(x) qu[++qr]=x%10^48,x/=10;
while(qr) putchar(qu[qr--]);
}
inline void Puts(const char*s){
for(int i=0;s[i];++i)
putchar(s[i]);
putchar('\n');
}
struct Flusher_{~Flusher_(){flush();}}io_flusher_;
}
using IO::read;
using IO::write;
const int maxn = 4e5+114;
struct node{
int u,v,id;
int k,b;
char type;
//u 在上面 v 在下面
}cluster[maxn];
int n,m,a[maxn];
int pos[maxn],fa[maxn],ls[maxn],rs[maxn];
char type[maxn];//P 是边点 C 是 compress 点 R 是 rake 点
int root=1;//根簇
void compress(node x,node y,node &w){
//x 在上面 y 在下面
w.u=x.u;
w.v=y.v;
w.k=1ll*x.k*y.k%mod;
w.b=(1ll*x.k*y.b%mod+1ll*x.k*a[x.v]%mod+x.b)%mod;
pos[x.v]=w.id;
fa[x.id]=fa[y.id]=w.id;
ls[w.id]=x.id;
rs[w.id]=y.id;
//cout<<"compress"<<w.u<<" "<<w.v<<" "<<w.ans<<'\n';
w.type='C';
root=w.id;
}
void rake(node x,node y,node &w){
//把 x rake 到 y 上
w.u=x.u;
w.v=y.v;
w.k=1ll*y.k*(1ll*x.k*a[x.v]%mod+x.b)%mod;
w.b=1ll*y.b*(1ll*x.k*a[x.v]%mod+x.b)%mod;
pos[x.v]=w.id;
fa[x.id]=fa[y.id]=w.id;
ls[w.id]=x.id;
rs[w.id]=y.id;
//cout<<"rake"<<w.u<<' '<<w.v<<' '<<w.ans<<'\n';
w.type='R';
root=w.id;
}
void update(int u){
if(u==0) return ;
if(cluster[u].type=='C'){
compress(cluster[ls[u]],cluster[rs[u]],cluster[u]);
update(fa[u]);
}else{
rake(cluster[ls[u]],cluster[rs[u]],cluster[u]);
update(fa[u]);
}
}
vector<int> E[maxn];
int father_pos[maxn];//一个点到其父亲的边的簇编号
int father[maxn];
int son[maxn],sz[maxn],tot;
vector<int> st[maxn];//重链上的点存到链顶
void dfs1(int u){
sz[u]=1;
for(int v:E[u]){
if(v==father[u]) continue;
father[v]=u;
father_pos[v]=++tot;
cluster[tot].u=u,cluster[tot].v=v,cluster[tot].id=tot,cluster[tot].k=1,cluster[tot].b=0;
dfs1(v);
if(sz[v]>sz[son[u]]) son[u]=v;
sz[u]+=sz[v];
}
}
void dfs2(int u,int tp){
st[tp].push_back(u);
if(son[u]!=0) dfs2(son[u],tp);
for(int v:E[u]){
if(v==father[u]||v==son[u]) continue;
dfs2(v,v);
}
}
vector<int> vec[maxn];
vector<int> pre[maxn];
int solve(int l,int r,int u){
if(l>r) return 0;
if(l==r) return father_pos[vec[u][l]];
int L=l,R=r;
while(L+1<R){
int mid=(L+R)>>1;
if((pre[u][mid]-pre[u][l-1])*2<=(pre[u][r]-pre[u][l-1])) L=mid;
else R=mid;
}
int mid=L;
int lson=solve(l,mid,u);
int rson=solve(mid+1,r,u);
int res=++tot;
cluster[tot].id=tot;
rake(cluster[lson],cluster[rson],cluster[res]);
return res;
}
int calc(int l,int r,int u){
if(l>r) return 0;
if(l==r) return father_pos[vec[u][l]];
int L=l,R=r;
while(L+1<R){
int mid=(L+R)>>1;
if((pre[u][mid]-pre[u][l-1])*2<=(pre[u][r]-pre[u][l-1])) L=mid;
else R=mid;
}
int mid=L;
int lson=calc(l,mid,u);
int rson=calc(mid+1,r,u);
int res=++tot;
cluster[tot].id=tot;
compress(cluster[lson],cluster[rson],cluster[res]);
return res;
}
void dfs3(int u){
for(int x:st[u]){
if(son[x]==0) continue;
pre[x].push_back(0);
vec[x].push_back(0);
for(int v:E[x]){
if(v!=son[x]&&v!=father[x]){
dfs3(v);
//收缩 (x,v) 一个簇
vec[x].push_back(v);
}
}
//在对这些轻儿子簇按中点分治的方法合并起来
for(int i=1;i<=vec[x].size()-1;i++){
pre[x].push_back(pre[x][i-1]+sz[vec[x][i]]);
}
int rt=solve(1,vec[x].size()-1,x);
if(rt!=0){
tot++;
cluster[tot].id=tot;
rake(cluster[rt],cluster[father_pos[son[x]]],cluster[tot]);
father_pos[son[x]]=tot;//rake 到重链上
}
}
vec[u].clear();
pre[u].clear();
pre[u].push_back(0);
vec[u].push_back(0);
for(int x:st[u]){
vec[u].push_back(x);
}
for(int i=1;i<=vec[u].size()-1;i++){
pre[u].push_back(pre[u][i-1]+sz[father[vec[u][i]]]-sz[vec[u][i]]);
}
if(u!=1) father_pos[u]=calc(1,vec[u].size()-1,u);//把重链上的边 compress 成一条
else father_pos[u]=calc(2,vec[u].size()-1,u);
E[u].clear();
E[u].push_back(father[u]);
return ;
}
int sum;
int main(){
read(n);
read(m);
for(int i=2;i<=n;i++){
int p;
read(p);
E[p].push_back(i);
E[i].push_back(p);
}
for(int i=1;i<=n;i++) read(a[i]);
dfs1(1);
dfs2(1,1);
dfs3(1);
while(m--){
int x,v;
read(x);
read(v);
a[x]=v;
update(pos[x]);
write(((1ll*cluster[root].k*a[cluster[root].v]+cluster[root].b)+a[cluster[root].u])%mod);
putchar('\n');
}
return 0;
}

分治计算贡献

洛谷 P3781 切树游戏

注意到 Top tree 本身可以是一种树分治。

按照 Top tree 维护点集的套路,我们不将上下界点的异或值计入状态贡献,但是还是要开 dp 数组记录上下界点选或不选的 dp 值。

定义 \(F_i,G_i,D_i,Z_i\) 分别表示簇内选出一些点(不能选一个界点,可以选一个其他节点或者两个界点)且只包含上界点,只包含下界点,同时包含上下界点,上下界点均不包含的答案。有如下转移式(我们均认为将簇 \(x,y\) 合并为簇 \(w\)):

对于一个 compress 节点:

\(F_{w,i} = \sum_{j \oplus k = i \oplus a_{v}} D_{x,j} \times F_{y,k} + F_{x,i} + D_{x,{i \oplus a_v}}\)

\(G_{w,i} = \sum_{j \oplus k = i \oplus a_v} G_{x,j} \times D_{y,k} + G_{y,i} + D_{y,{i \oplus a_v}}\)

\(D_{w,i} = \sum_{j \oplus k = i \oplus a_v} D_{x,j} \times D_{y,k}\)

\(Z_{w,i} = \sum_{j \oplus k = i \oplus a_v} G_{x,j} \times F_{y,k} + G_{x,i \oplus a_v} + F_{y,i \oplus a_v} + \left[i = a_v \right]\)

对于一个 rake 节点:

\(F_{w,i} = \sum_{j \oplus k = i} F_{x,j} \times F_{y,k} + \sum_{j \oplus k = i \oplus a_v} D_{x,j} \times F_{y,k} + F_{x,i} + F_{y,i} + D_{x,i \oplus a_v}\)

\(G_{w,i} = G_{y,i}\)

\(D_{w,i} = \sum_{j \oplus k = i} F_{x,j} \times D_{y,k} + \sum_{j \oplus k = i \oplus a_v} D_{x,j} \times D_{y,k} + D_{y,i}\)

\(Z_{u,i} = Z_{x,i} + G_{x,i \oplus a_v} + Z_{y,i}\)

然后你发现转移的瓶颈是异或卷积以及异或平移下标,第一个可以将原 dp 数组转变为 fwt 处理后的形式,第二个则可以这么解决:

\(a_{i \oplus C} = \sum_{j \oplus k = i} a_i \times \left[k = C \right] = a \otimes \left[i = C \right]\)

从而转变为异或卷积形式用 fwt 处理后的数组解决。

#include<bits/stdc++.h>
#define int long long
const int mod = 10007;
using namespace std;
const int maxn = 6e4+114;
const int maxv = 128;
struct node{
int u,v,id;
int f[maxv],g[maxv],d[maxv],z[maxv];
char type;
}cluster[maxn];
int tran[maxv][maxv];
int n,m;
int a[maxn];
int pos[maxn],fa[maxn],ls[maxn],rs[maxn];
int root=1;
void compress(node x,node y,node &w){
w.u=x.u;
w.v=y.v;
for(int i=0;i<maxv;i++){
w.f[i]=((x.d[i]*y.f[i]*tran[a[x.v]][i] + x.f[i] + x.d[i]*tran[a[x.v]][i])%mod);
w.g[i]=((x.g[i]*y.d[i]*tran[a[x.v]][i] + y.g[i] + y.d[i]*tran[a[x.v]][i]%mod)%mod);
w.d[i]=((x.d[i]*y.d[i]*tran[a[x.v]][i])%mod);
w.z[i]=((x.g[i]*y.f[i]*tran[a[x.v]][i] + x.g[i]*tran[a[x.v]][i] + y.f[i]*tran[a[x.v]][i] + tran[a[x.v]][i]+x.z[i]+y.z[i])%mod);
}
pos[x.v]=w.id;
fa[x.id]=fa[y.id]=w.id;
ls[w.id]=x.id;
rs[w.id]=y.id;
w.type='C';
root=w.id;
}
void rake(node x,node y,node &w){
//把 x rake 到 y 上
w.u=x.u;
w.v=y.v;
for(int i=0;i<maxv;i++){
w.f[i]=((x.f[i]*y.f[i] + x.d[i]*y.f[i]*tran[a[x.v]][i] + x.f[i] + y.f[i] + x.d[i]*tran[a[x.v]][i])%mod);
w.g[i]=((y.g[i])%mod);
w.d[i]=((x.f[i]*y.d[i] + x.d[i]*y.d[i]*tran[a[x.v]][i] + y.d[i])%mod);
w.z[i]=((x.z[i] + x.g[i]*tran[a[x.v]][i] + y.z[i] + tran[a[x.v]][i])%mod);
}
pos[x.v]=w.id;
fa[x.id]=fa[y.id]=w.id;
ls[w.id]=x.id;
rs[w.id]=y.id;
w.type='R';
root=w.id;
}
void update(int u){
if(u==0) return ;
if(cluster[u].type=='C'){
compress(cluster[ls[u]],cluster[rs[u]],cluster[u]);
update(fa[u]);
}else{
rake(cluster[ls[u]],cluster[rs[u]],cluster[u]);
update(fa[u]);
}
}
vector<int> E[maxn];
int father_pos[maxn];
int father[maxn];
int son[maxn],sz[maxn],tot;
vector<int> st[maxn];
int F[maxv],G[maxv],D[maxv],Z[maxv];
void dfs1(int u){
sz[u]=1;
for(int v:E[u]){
if(v==father[u]) continue;
father[v]=u;
father_pos[v]=++tot;
cluster[tot].u=u,cluster[tot].v=v,cluster[tot].id=tot;
for(int i=0;i<maxv;i++) cluster[tot].f[i]=F[i],cluster[tot].g[i]=(G[i]),cluster[tot].d[i]=(D[i]),cluster[tot].z[i]=(Z[i]);
dfs1(v);
if(sz[v]>sz[son[u]]) son[u]=v;
sz[u]+=sz[v];
}
}
void dfs2(int u,int tp){
st[tp].push_back(u);
if(son[u]!=0) dfs2(son[u],tp);
for(int v:E[u]){
if(v==father[u]||v==son[u]) continue;
dfs2(v,v);
}
}
vector<int> vec[maxn];
vector<int> pre[maxn];
int solve(int l,int r,int u,char type){
if(l>r) return 0;
if(l==r) return father_pos[vec[u][l]];
int L=l,R=r;
while(L+1<R){
int mid=(L+R)>>1;
if((pre[u][mid]-pre[u][l-1])*2<=(pre[u][r]-pre[u][l-1])) L=mid;
else R=mid;
}
int mid=L;
int lson=solve(l,mid,u,type);
int rson=solve(mid+1,r,u,type);
int res=++tot;
cluster[tot].id=tot;
if(type=='R') rake(cluster[lson],cluster[rson],cluster[res]);
else compress(cluster[lson],cluster[rson],cluster[res]);
return res;
}
void dfs3(int u){
for(int x:st[u]){
if(son[x]==0) continue;
pre[x].push_back(0);
vec[x].push_back(0);
for(int v:E[x]){
if(v!=son[x]&&v!=father[x]){
dfs3(v);
vec[x].push_back(v);
}
}
for(int i=1;i<=vec[x].size()-1;i++){
pre[x].push_back(pre[x][i-1]+sz[vec[x][i]]);
}
int rt=solve(1,vec[x].size()-1,x,'R');
if(rt!=0){
tot++;
cluster[tot].id=tot;
rake(cluster[rt],cluster[father_pos[son[x]]],cluster[tot]);
father_pos[son[x]]=tot;
}
}
vec[u].clear();
pre[u].clear();
pre[u].push_back(0);
vec[u].push_back(0);
for(int x:st[u]){
vec[u].push_back(x);
}
for(int i=1;i<=vec[u].size()-1;i++){
pre[u].push_back(pre[u][i-1]+sz[father[vec[u][i]]]-sz[vec[u][i]]);
}
if(u!=1) father_pos[u]=solve(1,vec[u].size()-1,u,'C');
else father_pos[u]=solve(2,vec[u].size()-1,u,'C');
E[u].clear();
E[u].push_back(father[u]);
return ;
}
void fwt_xor(int *Q,int x=1){
for(int o=2,k=1;o<=maxv;o<<=1,k<<=1){
for(int i=0;i<maxv;i+=o)
for(int j=0;j<k;j++) Q[i+j]=(Q[i+j]+Q[i+j+k])%mod,Q[i+j+k]=(Q[i+j]+mod+mod-Q[i+j+k]-Q[i+j+k])%mod,Q[i+j]=Q[i+j]*x%mod,Q[i+j+k]=Q[i+j+k]*x%mod;
}
}
int mx;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
D[0]=1;
fwt_xor(F);
fwt_xor(G);
fwt_xor(D);
fwt_xor(Z);
for(int i=0;i<maxv;i++){
tran[i][i]=1;
fwt_xor(tran[i]);
}
cin>>n>>mx;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=2;i<=n;i++){
int u,v;
cin>>u>>v;
E[u].push_back(v);
E[v].push_back(u);
}
dfs1(1);
dfs2(1,1);
dfs3(1);
cin>>m;
while(m--){
string opt;
cin>>opt;
if(opt=="Change"){
int x,y;
cin>>x>>y;
a[x]=y;
update(pos[x]);
}else{
int k;
cin>>k;
for(int i=0;i<maxv;i++) F[i]=cluster[root].f[i],G[i]=cluster[root].g[i],D[i]=cluster[root].d[i],Z[i]=cluster[root].z[i];
fwt_xor(F,(mod+1)/2);
fwt_xor(G,(mod+1)/2);
fwt_xor(D,(mod+1)/2);
fwt_xor(Z,(mod+1)/2);
cout<<(F[k^a[cluster[root].u]]+G[k^a[cluster[root].v]]+D[k^a[cluster[root].u]^a[cluster[root].v]]+Z[k]+(k==a[cluster[root].u])+(k==a[cluster[root].v]))%mod<<'\n';
}
}
return 0;
}

邻域上的 dp

能维护子树链的 dp 的结构不少,看看邻域上的 dp 的维护。

P8498 树上邻域数点

找到最大的被给定邻域覆盖的簇,它在 Top tree 上的子树内的簇同样被完全覆盖,提示我们可以处理出每个邻域的信息,而由于它的父亲没有被完全覆盖,所以 \(d < sz_{fa}\),并且在这个簇外的邻域形如以界点为根的子树中深度不超过 \(d - dis_{u,v}\) 的所有点的深度信息,而这个深度显然小于 \(sz_{fa}\),并且对于一个父亲簇需要处理的界点只有 \(4\) 个,而 Top tree 的树高为 \(\log n\) 代表其子树大小和为 \(O(n \log n)\) 级别。总之,如果我们有办法求出这 \(O(n \log n)\) 个信息,就有可能做到快速合并。

由于状态总量很少,考虑 dp。

我们定义 \(dp_{u,0/1,i}\) 表示簇 \(u\) 的上界点与下界点在其簇内的 \(i\) 邻域信息,显然有 \(i \leq sz_u\),所以在构建 Top tree 时可以在 rake 与 compress 时暴力合并,总合并量级还是 \(O(n \log n)\) 的。同时在这个过程中顺便维护出每个簇所有边集与以其两个界点为点集的信息,

然后考虑换根 dp。

定义 \(g_{u,0/1,i}\) 表示簇 \(u\) 的上界点与下界点在簇外的 \(i\) 邻域信息,我们在知道 \(g_{u,0/1,i}\) 与 \(f_{ls_{u},0/1.i}\) 后可以 \(O(sz_{u})\) 地求出 \(g_{u,0/1,i}\) 这个状态数位 \(O(sz_u)\) 个的 dp 数组。那么便从上到下的 dp 求解出所有 \(g_{u,0/1,i}\)。

#ifndef CIRCLE_H
#define CIRCLE_H
#include<vector>
struct info{
unsigned val;
unsigned poi[2];
};
const info emptyinfo=info{0,(unsigned)-1,(unsigned)-1};
info MR(info a,info b);
info MC(info a,info b);
void init(int T,int n,int q,std::vector<int>dad,std::vector<info>ve,int M);
bool isempty(info a);
info ask(int x,int d);
#endif
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5+114;
vector<int> E[maxn];
info edge[maxn];//点 i 到其父亲的信息
struct node{
int u,v,id,dis;//包括界点
int len,maxu,maxv;//维护直径
vector<info> fu,fv,gu,gv;//子树内距离两个界点 k 邻域信息/子树外距离两个界点 k 邻域信息 第一个处理到自己簇大小 第二个处理到父亲簇大小
info all;//整个簇的信息
char type;
//u 在上面 v 在下面
}cluster[maxn];
int pos[maxn],fa[maxn],ls[maxn],rs[maxn];//pos 表示每个点所在的最小簇
char type[maxn];//P 是边点 C 是 compress 点 R 是 rake 点
int root=1;//根簇
info R(info a,info b){
if(isempty(a)==true) return b;
if(isempty(b)==true) return a;
return MR(a,b);
}
info C(info a,info b){
if(isempty(a)==true) return b;
if(isempty(b)==true) return a;
return MC(a,b);
}
info queryf(node &u,int p,char type){
if(p>=(int)u.fu.size()) p=(int)u.fu.size()-1;
if(p<0) return emptyinfo;
else return (type=='u'?u.fu[p]:u.fv[p]);
}
info queryg(node &u,int p,char type){
if(p>=(int)u.gu.size()) p=(int)u.gu.size()-1;
if(p<0) return emptyinfo;
else return (type=='u'?u.gu[p]:u.gv[p]);
}
void compress(node &x,node &y,node &w){
//x 在上面 y 在下面
w.u=x.u,w.v=y.v;
w.len=max(max(x.len,y.len),x.maxv+y.maxu);
w.maxu=max(x.maxu,x.dis+y.maxu);
w.maxv=max(y.maxv,y.dis+x.maxv);
w.dis=x.dis+y.dis;
w.all=C(x.all,y.all);
w.fu.push_back(emptyinfo);
w.fv.push_back(emptyinfo);
for(int i=1;i<=w.len;i++){
w.fu.push_back(C(queryf(x,i,'u'),queryf(y,i-x.dis,'u')));
w.fv.push_back(C(queryf(x,i-y.dis,'v'),queryf(y,i,'v')));
}
fa[x.id]=fa[y.id]=w.id;
ls[w.id]=x.id;
rs[w.id]=y.id;
w.type='C';
root=w.id;
}
void rake(node &x,node &y,node &w){
//把 x rake 到 y 上
w.u=y.u,w.v=y.v;
w.len=max(max(x.len,y.len),y.maxu+x.maxu);
w.maxu=max(x.maxu,y.maxu);
w.maxv=max(y.maxv,x.maxu+y.dis);
w.dis=y.dis;
w.all=R(y.all,x.all);
w.fu.push_back(emptyinfo);
w.fv.push_back(emptyinfo);
for(int i=1;i<=w.len;i++){
w.fu.push_back(R(queryf(y,i,'u'),queryf(x,i,'u')));
w.fv.push_back(R(queryf(y,i,'v'),queryf(x,i-y.dis,'u')));
}
fa[x.id]=fa[y.id]=w.id;
ls[w.id]=x.id;
rs[w.id]=y.id;
w.type='R';
root=w.id;
}
int father_pos[maxn];//一个点到其父亲的边的簇编号
int father[maxn];
int son[maxn],sz[maxn],tot,dep[maxn];
int top[maxn];
vector<int> st[maxn];//重链上的点存到链顶
void dfs1(int u){
sz[u]=1;
for(int v:E[u]){
dep[v]=dep[u]+1;
father[v]=u;
father_pos[v]=++tot;
pos[u]=pos[v]=tot;
cluster[tot].u=u,cluster[tot].v=v,cluster[tot].id=tot,cluster[tot].dis=1,cluster[tot].len=1,cluster[tot].maxu=1,cluster[tot].maxv=1,cluster[tot].all=edge[v],cluster[tot].fu.push_back(emptyinfo),cluster[tot].fu.push_back(edge[v]),cluster[tot].fv.push_back(emptyinfo),cluster[tot].fv.push_back(edge[v]);
dfs1(v);
if(sz[v]>sz[son[u]]) son[u]=v;
sz[u]+=sz[v];
}
}
void dfs2(int u,int tp){
top[u]=tp;
st[tp].push_back(u);
if(son[u]!=0) dfs2(son[u],tp);
for(int v:E[u]){
if(v==son[u]) continue;
dfs2(v,v);
}
}
int LCA(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u=father[top[u]];
}
if(dep[u]<dep[v]) swap(u,v);
return v;
}
int dis(int u,int v){
return dep[u]+dep[v]-2*dep[LCA(u,v)];
}
vector<int> vec[maxn];
vector<int> pre[maxn];
int solve(int l,int r,int u){
if(l==r) return father_pos[vec[u][l]];
int L=l,R=r;
while(L+1<R){
int mid=(L+R)>>1;
if((pre[u][mid]-pre[u][l-1])*2<=(pre[u][r]-pre[u][l-1])) L=mid;
else R=mid;
}
int mid=L;
int lson=solve(l,mid,u);
int rson=solve(mid+1,r,u);
int res=++tot;
cluster[tot].id=tot;
rake(cluster[lson],cluster[rson],cluster[res]);
return res;
}
int calc(int l,int r,int u){
if(l==r) return father_pos[vec[u][l]];
int L=l,R=r;
while(L+1<R){
int mid=(L+R)>>1;
if((pre[u][mid]-pre[u][l-1])*2<=(pre[u][r]-pre[u][l-1])) L=mid;
else R=mid;
}
int mid=L;
int lson=calc(l,mid,u);
int rson=calc(mid+1,r,u);
int res=++tot;
cluster[tot].id=tot;
compress(cluster[lson],cluster[rson],cluster[res]);
return res;
}
void dfs3(int u){
for(int x:st[u]){
if(son[x]==0) continue;
pre[x].push_back(0);
vec[x].push_back(0);
for(int v:E[x]){
if(v!=son[x]){
dfs3(v);
//收缩 (x,v) 一个簇
vec[x].push_back(v);
}
}
//在对这些轻儿子簇按中点分治的方法合并起来
for(int i=1;i<=(int)vec[x].size()-1;i++){
pre[x].push_back(pre[x][i-1]+sz[vec[x][i]]);
}
if(vec[x].size()>=2){
int rt=solve(1,(int)vec[x].size()-1,x);
if(rt!=0){
tot++;
cluster[tot].id=tot;
rake(cluster[rt],cluster[father_pos[son[x]]],cluster[tot]);
father_pos[son[x]]=tot;//rake 到重链上
}
}
}
vec[u].clear();
pre[u].clear();
pre[u].push_back(0);
vec[u].push_back(0);
for(int x:st[u]){
vec[u].push_back(x);
}
for(int i=1;i<=(int)vec[u].size()-1;i++){
pre[u].push_back(pre[u][i-1]+sz[father[vec[u][i]]]-sz[vec[u][i]]);
}
if(u!=1) father_pos[u]=calc(1,(int)vec[u].size()-1,u);//把重链上的边 compress 成一条
else father_pos[u]=calc(2,(int)vec[u].size()-1,u);
E[u].clear();
E[u].push_back(father[u]);
return ;
}
void DP(int u){
if(ls[u]==0) return ;
if(cluster[u].type=='C'){
cluster[ls[u]].gu.push_back(emptyinfo);
cluster[ls[u]].gv.push_back(emptyinfo);
for(int i=1;i<=cluster[u].len;i++) cluster[ls[u]].gu.push_back(queryg(cluster[u],i,'u')),cluster[ls[u]].gv.push_back(C(queryf(cluster[rs[u]],i,'u'),queryg(cluster[u],i-cluster[rs[u]].dis,'v')));
cluster[rs[u]].gu.push_back(emptyinfo);
cluster[rs[u]].gv.push_back(emptyinfo);
for(int i=1;i<=cluster[u].len;i++) cluster[rs[u]].gu.push_back(C(queryf(cluster[ls[u]],i,'v'),queryg(cluster[u],i-cluster[ls[u]].dis,'u'))),cluster[rs[u]].gv.push_back(queryg(cluster[u],i,'v'));
}else{
cluster[ls[u]].gu.push_back(emptyinfo);
cluster[ls[u]].gv.push_back(emptyinfo);
for(int i=1;i<=cluster[u].len;i++) cluster[ls[u]].gu.push_back(R(queryg(cluster[u],i,'u'),C(queryf(cluster[rs[u]],i,'u'),queryg(cluster[u],i-cluster[rs[u]].dis,'v')))),cluster[ls[u]].gv.push_back(emptyinfo);
cluster[rs[u]].gu.push_back(emptyinfo);
cluster[rs[u]].gv.push_back(emptyinfo);
for(int i=1;i<=cluster[u].len;i++) cluster[rs[u]].gu.push_back(R(queryg(cluster[u],i,'u'),queryf(cluster[ls[u]],i,'u'))),cluster[rs[u]].gv.push_back(queryg(cluster[u],i,'v'));
}
DP(ls[u]);
DP(rs[u]);
//默认将界点 u 的簇外信息合并上自己簇的信息
for(int i=0;i<=cluster[u].len;i++){
cluster[ls[u]].gu[i]=C(cluster[ls[u]].gu[i],cluster[ls[u]].all);
cluster[rs[u]].gu[i]=C(cluster[rs[u]].gu[i],cluster[rs[u]].all);
}
}//Top tree 上换根 dp
char check(node &u,int p){
if(p==u.u) return 'u';
else return 'v';
}
info ask(int u, int d){
if(d==0) return emptyinfo;
int now=pos[u];
while(cluster[fa[now]].len<d) now=fa[now];
return C(queryg(cluster[now],d-dis(cluster[now].u,u),'u'),queryg(cluster[now],d-dis(cluster[now].v,u),'v'));
}
void init(int T, int n, int q, vector<int> FA, vector<info> e, int M){
for(int i=1;i<n;i++){
E[FA[i-1]].push_back(i+1);
edge[i+1]=e[i-1];
}
dfs1(1);
dfs2(1,1);
dfs3(1);
DP(root);
cluster[root].len=n;
return ;
}

其他应用

待补充。

静态 top tree 入门的更多相关文章

  1. bzoj2049 [Sdoi2008]Cave 洞穴勘测 link cut tree入门

    link cut tree入门题 首先说明本人只会写自底向上的数组版(都说了不写指针.不写自顶向下QAQ……) 突然发现link cut tree不难写... 说一下各个函数作用: bool isro ...

  2. 动态树(LCT、Top Tree、ETT)

    LCT Upd: 一个细节:假如我们要修改某个节点的数据,那么要先把它makeroot再修改,改完之后pushup. LCT是一种维护森林的数据结构,本质是用Splay维护实链剖分. 实链剖分大概是这 ...

  3. P6106 [Ynoi2010] Self Adjusting Top Tree

    P6106 [Ynoi2010] Self Adjusting Top Tree 题意 给出平面直角坐标系上若干不与坐标轴平行的处于第一象限的互不相交的线段,多次询问平面中一个第一象限的矩形与这些线段 ...

  4. link cut tree 入门

    鉴于最近写bzoj还有51nod都出现写不动的现象,决定学习一波厉害的算法/数据结构. link cut tree:研究popoqqq那个神ppt. bzoj1036:维护access操作就可以了. ...

  5. 基于 Vue.js 之 iView UI 框架非工程化实践记要 使用 Newtonsoft.Json 操作 JSON 字符串 基于.net core实现项目自动编译、并生成nuget包 webpack + vue 在dev和production模式下的小小区别 这样入门asp.net core 之 静态文件 这样入门asp.net core,如何

    基于 Vue.js 之 iView UI 框架非工程化实践记要   像我们平日里做惯了 Java 或者 .NET 这种后端程序员,对于前端的认识还常常停留在 jQuery 时代,包括其插件在需要时就引 ...

  6. dsu on tree入门

    先瞎扯几句 说起来我跟这个算法好像还有很深的渊源呢qwq.当时在学业水平考试的考场上,题目都做完了不会做,于是开始xjb出题.突然我想到这么一个题 看起来好像很可做的样子,然而直到考试完我都只想出来一 ...

  7. [HNOI2002]营业额统计 Splay tree入门题

    题目连接:http://www.lydsy.com/JudgeOnline/problem.php?id=1588 1588: [HNOI2002]营业额统计 Time Limit: 5 Sec   ...

  8. K-D tree入门

    久仰K-D tree大名已久,终于在合适的时候遇见了合适的水题入了坑入了门 K-D tree是什么 K-D tree是什么? 按名字上翻译来就是K维的树,就是一个用来维护K维空间的点的平衡二叉树 K- ...

  9. 手撸红黑树-Red-Black Tree 入门

    一.学习红黑树前的准备: 熟悉基础数据结构 了解二叉树概念 二.红黑树的规则和规则分析: 根节点是黑色的 所有叶子节点(Null)是黑色的,一般会认定节点下空节点全部为黑色 如果节点为红色,那么子节点 ...

  10. dsu on tree 入门

    Dus on tree 树上并查集?. 啊这,并不是的啦,他利用了树上启发式合并的思想. 他主要解决不带修改且主要询问子树信息的树上问题. 先来看到例题,CF600E . 这不就是树上莫队的经典题吗? ...

随机推荐

  1. 🔥fastposter v2.19.0 一款很哇塞的海报生成器

    fastposter v2.19.0 一款很哇塞的海报生成器 fastposter 是一款快速开发海报的工具,已经服务众多电商.分销系统.电商海报.电商主图.行业海报等海报生成和制作场景. 什么是 f ...

  2. 在Biwen.QuickApi中整合一个极简的发布订阅(事件总线)

    闲来无聊在我的Biwen.QuickApi中实现一下极简的事件总线,其实代码还是蛮简单的,对于初学者可能有些帮助 就贴出来,有什么不足的地方也欢迎板砖交流~ 首先定义一个事件约定的空接口 public ...

  3. 网络拓扑—DNS服务搭建

    目录 DNS服务搭建 网络拓扑 配置网络 DNS PC 安装DNS服务 配置DNS服务 创建正向查找区域 创建反向查找区域 创建子域名 PC机DNS域名解析 DNS服务搭建 网络拓扑 为了节省我的U盘 ...

  4. 基于FPGA的电子琴设计(按键和蜂鸣器)----第一版

    欢迎各位朋友关注"郝旭帅电子设计团队",本篇为各位朋友介绍基于FPGA的电子琴设计(按键和蜂鸣器)----第一版. 功能说明: 外部输入七个按键,分别对应音符的"1.2. ...

  5. saltstack web 平台开发

    运维平台参考: https://wrapbootstrap.com/

  6. 当装饰者模式遇上Read Through缓存,一场技术的浪漫邂逅

    在<经验之谈:我为什么选择了这样一个激进的缓存大Key治理方案>一文中,我提到在系统中使用的缓存是旁路缓存模式,有读者朋友问,有没有用到过其他的缓存模式,本文将结合一个我曾经工作中的案例, ...

  7. GeneralUpdate .Net5 WPF、Winfrom、控制台应用自动更新组件

    https://www.bilibili.com/video/BV1aX4y137dd/?vd_source=43d3e66cc2ad46bac54dfb0c6a3a0a23 GeneralUpdat ...

  8. WPF基础之样式设置和模板化(三)

    IsItemsHost 属性在此示例中,一个必需的重要属性是 IsItemsHost 属性.IsItemsHost 属性用于指示在 ItemsControl(如处理项列表的 ListBox 控件)的模 ...

  9. Dockerfile-NGINX镜像制作

    1 NGINX镜像制作: 1.1 NGINX-dockerfile FROM centos:7 LABEL maintainer www.chenleilei.net RUN useradd www ...

  10. 继承,super,重写,多态,抽象,接口

    继承,super,重写,多态,抽象,接口 继承 extends 用于表示两个类之间的继承关系,继承是OOP的四大特性之一,他允许一个类(称之为子类或派送类) 继承另一个类(称之为父类或基类)的变量和方 ...