静态 top tree 入门

top,tree · 浏览次数 : 40

小编点评

```markdown ## 树上邻域数点找到最大的被给定邻域覆盖的簇 ### 背景 - 树上邻域数点问题是一个经典的问题,要求找出一个点集,使得它被给定的邻域完全覆盖。 - 本题要求在树上维护子树链的 dp,以便快速合并。 ### 解题思路 1. **维护子树链的 dp** - 定义 dp_{u,0/1,i} 表示簇 u 的上界点与下界点在其簇内的 i 邻域信息。 - 在构建 Top tree 时,可以在 rake 与 compress 时暴力合并,总合并量级是 O(n log n) 的。 - 同时在这个过程中顺便维护出每个簇所有边集与以其两个界点为点集的信息。 2. **换根 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}。 ### 实现细节 - 使用 fwt(快速傅里叶变换)处理 dp 数组,加速异或卷积运算。 - 维护子树链的 dp,利用换根 dp 求解簇外信息。 ### 代码实现 以下是使用 C++ 实现的代码示例,包括树的分治、dp 的计算以及最终结果输出。 ```cpp #include #include #include #include using namespace std; const int mod = 10007; const int maxn = 5e5 + 114; vector E[maxn]; info edge[maxn]; int n, m, a[maxn], pos[maxn], fa[maxn], ls[maxn], rs[maxn], type[maxn]; int root = 1; void dfs1(int u) { int sz[u] = 1; for (int v : E[u]) { if (v == fa[u]) continue; father[v] = u; father_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) { 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); } } 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 vec[maxn]; vector 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]); father_pos[son[u]] = tot; // rake 到重链上 } else { compress(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] && 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]]); } if (vec[x].size() >= 2) { 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]); } } int 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(C(queryf(cluster[ls[u]], i, 'u'), queryg(cluster[u], i - cluster[ls[u]].dis, '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[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); } } 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 FA, vector 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; } int main() { cin >> n >> m; for (int i = 2; i <= n; i++) { int p; cin >> p; E[p].push_back(i + 1); E[i + 1].push_back(p); } for (int i = 1; i <= n; i++) { cin >> a[i]; } dfs1(1); dfs2(1, 1); dfs3(1); while (m--) { int x, v; cin >> x >> v; a[x] = v; update(pos[x]); cout << ((1LL * cluster[root].k * a[cluster[root].v] + cluster[root].b) + a[cluster[root].u]) % mod<< endl; } return 0; } ``` ### 总结 通过维护子树链的 dp 和换根 dp,我们可以在 O(n log n) 的时间内解决树上邻域数点问题,并找到最大的被给定邻域覆盖的簇。

正文

理论

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

比如说线段树就通过分治的方式来通过将一个区间划分成 \(\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 入门相似的内容:

静态 top tree 入门

理论 我们需要一个数据结构维护树上的问题,仿照序列上的问题,我们需要一个方法快速的刻画出信息。 比如说线段树就通过分治的方式来通过将一个区间划分成 \(\log n\) 个区间并刻画出这 \(\log n\) 个区间的信息。 然后我们考虑把这个东西放到树上类比。你发现线段树上每个非叶节点都有两个儿子

[转帖]静态路由实例:如何在 macOS、FreeBSD、Linux、Windows、Cisco 和 VMware 上添加静态路由

https://sysin.org/blog/static-routing/ 学习一下呢. 本文描述主流系统和产品添加静态路由的方法,一些具备 WEB 管理界面的产品不在讨论范围,比如防火墙、路由器等多数产品具备直观的操作界面。 macOS 1、添加路由命令(临时) 与 Linux 类似,但是网关没

C静态库的创建与使用--为什么要引入静态库?

C源程序需要经过预处理、编译、汇编几个阶段,得到各自源文件对应的可重定位目标文件,可重定位目标文件就是各个源文件的二进制机器代码,一般是.o格式。比如:util1.c、util2.c及main.c三个C源文件,经过预处理器、编译器、汇编器的处理,就可以得到各自的目标文件util1.o,util2.o

创建framework静态库和.a静态库

在APP项目中使用的静态库有两种,一是.a静态库,另一种是framework静态库。下面分布介绍这2中静态库的创建过程,以及通过脚本工具做自动化打包的2种方式。 Framework静态库生成 如果APP项目和SDK项目都使用了pod第三方库,那么podfile文件设置如下: # App项目的Podf

Java静态变量在静态方法内部无法改变值

一、如何解决“Java静态变量在静态方法内部无法改变值”的问题 在Java中,静态变量(也称为类变量)属于类本身,而不是类的任何特定实例。它们可以在没有创建类的实例的情况下访问和修改。如果我们发现在静态方法内部无法改变静态变量的值,这通常是因为我们的代码中有一些逻辑错误或误解。 下面是一个简单的示例

Django 静态文件管理与部署指南

title: Django 静态文件管理与部署指南 date: 2024/5/10 17:38:36 updated: 2024/5/10 17:38:36 categories: 后端开发 tags: WebOpt CDN加速 DjangoCompress Webpack StaticDeploy

浅析静态应用安全测试

摘要:根据Forrester的 The State Of Application Security, 2022一文的预测,应用安全性的缺失将仍然是最常见的外部攻击方式,因此SAST将会在可预见的未来一直被重视。 本文分享自华为云社区《SAST-静态应用安全测试》,作者: gentle_zhou 。

.NET静态代码织入——肉夹馍(Rougamo)发布2.0

肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应用启动的初始化时间让服务更快可用,同时还能对静态方法进行AOP。 摆烂半年又一更,感谢各位的支持,那

基于React的SSG静态站点渲染方案

基于React的SSG静态站点渲染方案 静态站点生成SSG - Static Site Generation是一种在构建时生成静态HTML等文件资源的方法,其可以完全不需要服务端的运行,通过预先生成静态文件,实现快速的内容加载和高度的安全性。由于其生成的是纯静态资源,便可以利用CDN等方案以更低的成

vue3编译优化之“静态提升”

前言 在上一篇 vue3早已具备抛弃虚拟DOM的能力了文章中讲了对于动态节点,vue做的优化是将这些动态节点收集起来,然后当响应式变量修改后进行靶向更新。那么vue对静态节点有没有做什么优化呢?答案是:当然有,对于静态节点会进行“静态提升”。这篇文章我们来看看vue是如何进行静态提升的。 什么是静态