2025.1.2 日校内模拟赛总结(点分治,最短路/trick,01trie/trick)

文章目录

  • 时间安排
  • 题解
    • T1.[省选联考 2021 A/B 卷] 宝石
    • T2. [省选联考 2021 A/B 卷] 图函数
    • T3.[省选联考 2020 A 卷] 树

时间安排

  • 7 : 50 7:50 7:50 开题
  • 7 : 50 − 8 : 10 7:50 - 8:10 7:508:10 先把三道题都看了一遍 。 T 1 T1 T1 应该是个点分治,然后用 d s ds ds 维护信息。 T 2 T2 T2 有点神秘。 T 3 T3 T3 好像是山东省集的例题,但是当时就没有听懂。
  • 8 : 10 − 9 : 20 8:10 - 9:20 8:109:20 T 1 T1 T1。首先要求路径答案,所以可以点分治。然后需要求起点到重心从 1 1 1 开始最大能延伸到哪,以及重心到终点以某个数字为结尾最小可以连续到哪。然后发现这个可以写一个主席树每个点继承它的父亲的答案。查询只需要支持单点查和线段树二分即可。复杂度双 l o g log log。然后果断开冲。连写带调用了 45 m i n 45min 45min
  • 9 : 20 − 10 : 00 9:20 - 10:00 9:2010:00 T 2 T2 T2,中间的具体思考过程忘了,只是记得以为自己会了结果发现题看错导致假了。
  • 10 : 00 − 10 : 50 10:00 - 10:50 10:0010:50 重新思考 T 2 T2 T2,推出了一些结论。然后得到了一个 m 2 n m^2n m2n 的做法,可以拿到 44 p t s 44pts 44pts。写了一发,发现过不了,全都 T T T了。以为就是会被卡,过了一会儿测了一下大数据发现RE了,然后调了一下过了 44 p t s 44pts 44pts
  • 10 : 50 − 11 : 00 10:50 -11:00 10:5011:00 本想着去看看 T 3 T3 T3,但是看完一眼并没有什么很好的思路,只会 10 p t s 10pts 10pts 的暴力。而且感觉 T 2 T2 T2 还可以再拿一点分,就又回去看 T 2 T2 T2
  • 11 : 00 − 11 : 40 11:00 - 11:40 11:0011:40 一直再想 T 2 T2 T2 如何优化。最后还剩 30 m i n 30min 30min 意识到只需要求出每个点与关键点相互连通的最早时间,然后对答案数组差分一下就行。求时间只需要跑最短路即可。看一眼复杂度好像是 O ( n m log ⁡ 2 n ) O(nm\log_2n) O(nmlog2n) 的,但是我写了一个 s p f a spfa spfa 上去了,交上去有 80 p t s 80pts 80pts
  • 11 : 45 − 12 : 00 11:45 - 12:00 11451200 没啥时间了,感觉 T 2 T2 T2 就算改成 O ( n m log ⁡ 2 n ) O(nm\log_2n) O(nmlog2n) 也过不去,就去写 T 3 T3 T3 10 p t s 10pts 10pts 了。最后也是喜提没叫上。

得分:100 + 80 + 0
rk2
赛后才知道三道题都是之前省选的原题。

题解

T1.[省选联考 2021 A/B 卷] 宝石

原题:
[省选联考 2021 A/B 卷] 宝石

题意:
给定一棵 n n n 个点的树,每个点上有一个数 w i w_i wi,满足 w i ∈ [ 1 , m ] w_i \in[1, m] wi[1,m]。你有一个收容器,可以收集至多 c c c 个数,并且必须按照 P 1 , P 2 , . . . , P c P_1, P_2,...,P_c P1,P2,...,Pc 的顺序收集,其中 P i P_i Pi 互不相同,每种数最多被收集一次。现在有 q q q 次询问,每次询问给你一条有向路径 s → t s \to t st,问依次最多可以收集多少数字。

数据规模:
1 ≤ n , q ≤ 2 × 1 0 5 1 \leq n,q \leq 2 \times 10^5 1n,q2×105 1 ≤ c ≤ m ≤ 5 × 1 0 4 1 \leq c \leq m \leq 5 \times 10^4 1cm5×104

分析:
既然是处理路径询问,我们考虑 点分治
先将所有数 w i w_i wi 换成它在 P i P_i Pi 数组中的排名。如果没有这种数就赋值成 0 0 0
那么我们要求的就是一条有向路径上从 1 1 1 开始的最长 1 , 2 , . . . 1,2,... 1,2,... 子序列的长度。
考虑怎么求一条经过重心的路径的答案:
f i , x f_{i, x} fi,x 表示 i i i 号点到重心的路径上以 x x x 数字为开头每次递增 1 1 1 的子序列的结尾最大能到几。
g i , x g_{i, x} gi,x 表示重心到 i i i 号点的路径上以 x x x 数字为结尾每次递增 1 1 1 的子序列的开头最小能到几。
那么一条路径就是 max ⁡ x ( x ) ( g t , x ≤ f s , 1 ) \max\limits_{x}(x)(g_{t, x} \leq f_{s, 1}) xmax(x)(gt,xfs,1)
考虑怎么维护 f f f g g g。发现从重心开始往下递归的过程中 f x f_{x} fx 可以由它的父亲 f a fa fa f f a f_{fa} ffa 继承过来。只需要单点修改一个 f x , w x f_{x, w_x} fx,wx 即可。那么这个可以用主席树维护。
g g g 的维护方式同理。
那么对于线段树而言只需要维护区间最小值,能够支持单点查询。
查询答案时只需要单点查 f s , 1 f_{s, 1} fs,1,然后在 g t g_{t} gt 的线段树上二分即可。
复杂度 O ( n × log ⁡ 2 n × log ⁡ 2 m ) O(n \times \log_2n \times \log_2m) O(n×log2n×log2m)
CODE:

// 点分治 + 主席树 
#include<bits/stdc++.h>
#define pb emplace_back
#define MP make_pair
using namespace std;
typedef pair< int, int > PII;
const int N = 2e5 + 10;
int n, m, q, c, w[N];
int p[N], idx[N];
int ans[N];
vector< PII > qry[N];
vector< int > E[N];
int root, Maxn[N], sz[N], all;
bool vis[N]; // 每个点是否被标记过 
void get_root(int x, int fa) {
	sz[x] = 1; Maxn[x] = 0;
	for(auto v : E[x]) {
		if(v == fa || vis[v]) continue;
		get_root(v, x);
		Maxn[x] = max(Maxn[x], sz[v]);
		sz[x] += sz[v]; 
	}
	Maxn[x] = max(Maxn[x], all - sz[x]);
	if(Maxn[x] < Maxn[root]) root = x;
}
void get_sz(int x, int fa) {
	sz[x] = 1;
	for(auto v : E[x]) {
		if(v == fa || vis[v]) continue;
		get_sz(v, x);
		sz[x] += sz[v];
	}
}
int tot, Root[N]; // 每个点的根 
int bok[N]; // 每个点所属子树的标记 
struct SegmentTree {
	int ls, rs, mx, mn;
	#define ls(x) t[x].ls
	#define rs(x) t[x].rs
	#define mx(x) t[x].mx
	#define mn(x) t[x].mn
}t[N * 20];
int build() {
	tot ++;
	ls(tot) = rs(tot) = mx(tot) = 0;
	mn(tot) = n + 10;
	return tot;
}
void update(int p) {
	int lmx = (ls(p) ? mx(ls(p)) : 0), rmx = (rs(p) ? mx(rs(p)) : 0);
	int lmn = (ls(p) ? mn(ls(p)) : n + 10), rmn = (rs(p) ? mn(rs(p)) : n + 10);
	mx(p) = max(lmx, rmx);
	mn(p) = min(lmn, rmn);
}
int ask_max(int p, int lp, int rp, int pos) {
	if(lp == rp) return mx(p);
	int mid = (lp + rp >> 1);
	if(pos <= mid) return ask_max(ls(p), lp, mid, pos);
	else return ask_max(rs(p), mid + 1, rp, pos);
}
int ask_min(int p, int lp, int rp, int pos) {
	if(lp == rp) return mn(p);
	int mid = (lp + rp >> 1);
	if(pos <= mid) return ask_min(ls(p), lp, mid, pos);
	else return ask_min(rs(p), mid + 1, rp, pos);
}
int query(int p, int lp, int rp, int k) {
	if(mn(p) > k) return 0;
	if(lp == rp) return mn(p) <= k ? lp : 0;
	int mid = (lp + rp >> 1);
	if(rs(p) && mn(rs(p)) <= k) return query(rs(p), mid + 1, rp, k);
	else return query(ls(p), lp, mid, k);
}
int ins(int fa, int p, int lp, int rp, int c) { // 插入一个权值为 c 的点 
	int tp = build(); t[tp] = t[p];
	if(lp == rp) {
		if(c == 0) return tp;
		else {
			int v = ask_max(Root[fa], 0, n + 1, c + 1);
			if(v == 0) mx(tp) = c;
			else mx(tp) = v;
			v = ask_min(Root[fa], 0, n + 1, c - 1);
			if(v == 0) mn(tp) = c;
			else mn(tp) = v;
			return tp;
		}
	}
	int mid = (lp + rp >> 1);
	if(c <= mid) ls(tp) = ins(fa, ls(tp), lp, mid, c);
	else rs(tp) = ins(fa, rs(tp), mid + 1, rp, c);
	update(tp);
	return tp;
}
void dfs(int x, int fa, int bel) {
	bok[x] = bel;
	Root[x] = ins(fa, Root[fa], 0, n + 1, w[x]);
	for(auto v : E[x]) {
		if(v == fa || vis[v]) continue;
		dfs(v, x, bel);
	}
}
void Q(int x) {
	for(auto v : qry[x]) { // 枚举所有以 x 为 起点的询问 
		int id = v.first, u = v.second;
		if(bok[x] == bok[u] || ans[id] != -1) continue;
		int p = ask_max(Root[x], 0, n + 1, 1); // 查询 1 处的答案 
		int q = query(Root[u], 0, n + 1, p + 1); // 求最小值小于等于 p + 1 的 最大值 
		ans[id] = max(q, p);
	}
}
void dfs_ans(int x, int fa) {
	Q(x);
	for(auto v : E[x]) {
		if(v == fa || vis[v]) continue;
		dfs_ans(v, x);
	}
}
void solve(int rt) { // 处理所有经过 rt 的路径   每次用完都要清空主席树 
    vis[rt] = 1; tot = 0;
    Root[root] = ins(0, Root[0], 0, n + 1, w[rt]);
    bok[rt] = rt;
	for(auto v : E[rt]) {
		if(!vis[v]) dfs(v, rt, v);
	}
	Q(rt);
	for(auto v : E[rt]) {
		if(!vis[v]) dfs_ans(v, rt);
	}
	for(auto v : E[rt]) {
		if(vis[v]) continue;
		root = 0; all = sz[v];
		get_root(v, 0);
		get_sz(root, 0);
		solve(root);
	}
}
int main() {
	freopen("gem.in", "r", stdin);
	freopen("gem.out", "w", stdout);
	scanf("%d%d%d", &n, &m, &c);
	for(int i = 1; i <= c; i ++ ) {
		scanf("%d", &p[i]);
		idx[p[i]] = i;
	}
	for(int i = 1; i <= n; i ++ ) {
		scanf("%d", &w[i]);
		w[i] = idx[w[i]];
	}
	for(int i = 1; i < n; i ++ ) {
		int u, v; scanf("%d%d", &u, &v);
		E[u].pb(v); E[v].pb(u);
	}
	scanf("%d", &q);
	for(int i = 1; i <= q; i ++ ) {
		int s, t; scanf("%d%d", &s, &t);
		qry[s].pb(MP(i, t));
	}
	memset(ans, -1, sizeof ans);
	Maxn[0] = n + 1; all = n;
	get_root(1, 0);
	get_sz(root, 0);
	solve(root);
	for(int i = 1; i <= q; i ++ ) printf("%d\n", ans[i]);
	return 0;
}

T2. [省选联考 2021 A/B 卷] 图函数

感觉是一道非常有技巧的题。

原题:
[省选联考 2021 A/B 卷] 图函数

题意:
对于一张 n n n 个点 m m m 条边的有向图 G G G(顶点从 1 ∼ n 1 \sim n 1n 编号),定义函数 f ( u , G ) f(u, G) f(u,G)

  1. 初始化返回值 c n t = 0 cnt = 0 cnt=0,图 G ′ = G G' = G G=G
  2. 1 1 1 n n n 按顺序枚举顶点 v v v,如果当前的图 G ′ G' G 中,从 u u u v v v 与从 v v v u u u 的路径都存在,则将 c n t + 1 cnt + 1 cnt+1,并在图 G ′ G' G 中删去顶点 v v v 以及与它相关的边。
  3. 2 2 2 步结束后,返回值 c n t cnt cnt 即为函数值。

现在给定一张有向图 G G G,请你求出 h ( G ) = f ( 1 , G ) + f ( 2 , G ) + ⋯ + f ( n , G ) h(G) = f(1, G) + f(2, G) + \cdots + f(n, G) h(G)=f(1,G)+f(2,G)++f(n,G) 的值。

更进一步地,记删除(按输入顺序给出的)第 1 1 1 i i i 条边后的图为 G i G_i Gi 1 ≤ i ≤ m 1 \le i \le m 1im),请你求出所有 h ( G i ) h(G_i) h(Gi) 的值。

数据规模:

2 ≤ n ≤ 10 3 2 \le n \le {10}^3 2n103 1 ≤ m ≤ 2 × 10 5 1 \le m \le 2 \times {10}^5 1m2×105 1 ≤ x i , y i ≤ n 1 \le x_i, y_i \le n 1xi,yin

分析:
首先对于一张确定的图 G G G,我们将答案拆成所有点对 ( u , v ) ( u ≤ v ) (u, v)(u \leq v) (u,v)(uv) 的贡献。
含义是 f ( v , G ) f(v, G) f(v,G) u u u 会产生 1 1 1 的贡献。
那么需要满足 u ≤ v u \leq v uv。否则 v v v 会比 u u u 先删除因此枚举到 u u u 时一定删不了。

考虑 ( u , v ) (u, v) (u,v) 会产生贡献的条件:

相当于考虑过 1 ∼ u − 1 1 \sim u - 1 1u1 这些点的删除情况后 u u u v v v 仍然在一个强连通分量里。

在最开始一个点还没有删的条件下,图就会形成若干个强连通分量。那么 1 ∼ u − 1 1 \sim u - 1 1u1只可能 删除那些开始就和 v v v 在同一个强连通分量里的点。
但是换一个角度想:

对于一个点 i ∈ [ 1 , u − 1 ] i \in [1, u-1] i[1,u1],如果此时它和 v v v 不在一个强连通分量里,我们把它删掉不会影响后边本来能删的点,也不会影响最后 u u u 是否能删。
如果 i i i 可以被删掉,那么我们就把它删掉。

因此求 f ( v , G ) f(v, G) f(v,G) u u u 能被删掉的充要条件是 删掉 1 ∼ u − 1 1 \sim u - 1 1u1号点后 u u u 依然和 v v v 在一个强连通分量里

我们称删掉了 1 ∼ i 1 \sim i 1i 号点后 为 i i i 时刻。如果一个点也没删为 0 0 0 时刻。
那么对于一张图 G G G 而言: ∑ u f ( u , G ) \sum\limits_{u}f(u, G) uf(u,G) 的值为 i : 0 ∼ n − 1 i:0 \sim n - 1 i:0n1 时刻 i + 1 i + 1 i+1 所在的强连通分量的大小之和。
这样我们可以得到一个 O ( m 2 × n ) O(m^2 \times n) O(m2×n) 的做法:枚举每一张图,暴力对每一个时刻跑一遍 t a r j a n tarjan tarjan

下面是更优的做法:

更形式化的描述 ( u , v ) (u, v) (u,v) 对图 G G G 产生贡献的条件:相当于需要存在一条 只经过 ≥ u \geq u u 的点 u u u v v v 的路径,和一条从 v v v u u u 的路径。
考虑到从前往后删边图的连通性逐渐减弱。因此 ( u , v ) (u, v) (u,v) 产生贡献的图是一个前缀。
如果我们将边的 编号 看作 边权,一条路径的 长度 定义为 路径上所有边权的最小值
d i s u , v dis_{u, v} disu,v 表示 只经过 ≥ min ⁡ ( u , v ) \geq \min(u, v) min(u,v) 的点 u → v u \to v uv 的最长路,那么 0 ∼ min ⁡ ( d i s u , v , d i s v , u ) − 1 0 \sim \min(dis_{u, v}, dis_{v, u}) - 1 0min(disu,v,disv,u)1 就是 ( u , v ) (u, v) (u,v) 能产生贡献的前缀。

如果我们能求出来 d i s u , v dis_{u, v} disu,v,那么只需要枚举两个点差分一下就可以求出每张图的答案。问题在于怎么求 d i s dis dis

第一种做法是 从大到小 加点跑 f l o y d floyd floyd。但是这个需要卡常(也能过),具体写法见代码。
CODE:

#include<bits/stdc++.h>
#define pb emplace_back
using namespace std;
struct IO{
    static const int S=1<<21;
    char buf[S],*p1,*p2;int st[105],Top;
    ~IO(){clear();}
    inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
    inline void pc(const char c){Top==S&&(clear(),0);buf[Top++]=c;}
    inline char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
    inline IO&operator >> (char&x){while(x=gc(),x==' '||x=='\n'||x=='\r');return *this;}
    template<typename T>inline IO&operator >> (T&x){
        x=0;bool f=0;char ch=gc();
       while(!isdigit(ch)){if(ch=='-') f^=1;ch=gc();}
        while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
        f?x=-x:0;return *this;
    }
    inline IO&operator << (const char c){pc(c);return *this;}
    template<typename T>inline IO&operator << (T x){
        if(x<0) pc('-'),x=-x;
        do{st[++st[0]]=x%10,x/=10;}while(x);
        while(st[0]) pc('0'+st[st[0]--]);return *this;
    }
}fin,fout;
const int N = 1e3 + 10;
const int M = 2e5 + 10;
int n, m;
int f[N][N], ans[M];
int main() {
	fin >> n >> m;
	for(int i = 1; i <= m; i ++ ) {
		int u, v;
		fin >> u >> v;
		f[u][v] = max(f[u][v], i);
	}
    for(int i = 1; i <= n; i ++ ) f[i][i] = m + 1;
    for(int k = n; k >= 1; k -- ) { // 从大到小枚举中间点 
     	for(int i = 1; i <= n; i ++ ) {
     		if(!f[i][k]) continue; // 第一个剪枝
     		int lim = (i < k ? n : k - 1); // 第二个剪枝,相当于不能用 k 去更新 (i, j) 都大于 k 的点对。
     		for(int j = 1; j <= lim; j ++ ) 
			    f[i][j] = max(f[i][j], min(f[i][k], f[k][j]));
		}
	}
    for(int i = 1; i <= n; i ++ ) {
    	for(int j = i; j <= n; j ++ ) {
    	    ans[0] ++, ans[min(f[i][j], f[j][i])] --;
		}
	}
	for(int i = 0; i <= m; i ++ ) 
		if(i) ans[i] += ans[i - 1];
	for(int i = 0; i <= m; i ++ ) fout << ans[i], fout.pc(' ');
	return 0;
}

第二种做法:
考虑枚举一个 u u u,然后求出所有 d i s u , v ( u ≤ v ) dis_{u, v}(u \leq v) disu,v(uv) d i s v , u dis_{v, u} disv,u 只需要在反图上跑一遍即可。
然后按照边权从大到小插入每一条边,那么问题就变成了给你一张图和一个起点 s s s,依次插入一条有向边,要求出 最早的时刻 s s s 能到 v v v
这个问题感觉挺经典的??做法是:
假设我们插入了有向边 ( x , y ) (x, y) (x,y)
如果当前 x , y x, y x,y 都可达,那么这条边没用,直接跳过。
如果 x x x 可达 y y y 不可达,那么我们将 y y y 标记成可达然后以 y y y 为起点跑 b f s bfs bfs
如果 x x x 不可达并且 y y y 不可达,那么这条边只有在 x x x 可达的时候才有用。我们将它加入 x x x 的出边中。
每条边都只会被经过一次,复杂度 O ( m ) O(m) O(m)
这样总复杂度 O ( n m ) O(nm) O(nm)

CODE:

#include<bits/stdc++.h>
#define pb emplace_back
using namespace std;
const int M = 2e5 + 10;
const int N = 1e3 + 10;
int n, m, u[M], v[M];
bool bok[N];
namespace Sol2 {
    int ans[M];
    int T[2][N]; //  最小的最大 
    int head[N], tot;
    vector< int > G[N];
	void bfs(int s, int t) { // 相当于按照给定顺序插入一些有向边, 求 s 到每个点的最早时刻
	    for(int i = s; i <= n; i ++ ) T[t][i] = 0;
	    T[t][s] = m + 1;
	    tot = 0; for(int i = s; i <= n; i ++ ) G[i].clear(); // 清空边 
	    for(int i = m; i >= 1; i -- ) {
	    	int uu = u[i], vv = v[i];
	    	if(t == 1) swap(uu, vv);
	    	if(uu < s || vv < s) continue;
	    	if(T[t][vv]) continue;
	    	else if(!T[t][uu]) G[uu].pb(vv);
	    	else {
	    		T[t][vv] = i;
	    		queue< int > q; q.push(vv);
	    		while(!q.empty()) {
	    			int u = q.front(); q.pop();
	    			for(auto vt : G[u]) {
	    				if(T[t][vt]) continue;
	    				else T[t][vt] = i, q.push(vt);
					}
				//	G[u].clear();
				}
			}
		}
	}
    void work(int key) { 
 		bfs(key, 0); // 正反图 
 		bfs(key, 1);
 		for(int i = key; i <= n; i ++ ) { // 枚举每一个点,看它能删除的时刻 
 			int tt = min(T[0][i], T[1][i]);
 			ans[0] ++; ans[tt] --;
		}
	}
	void solve() { 
	    for(int i = n; i >= 1; i -- ) { // 枚举一个时刻对不同的 m 统计答案 
	        work(i); // i 是关键点 
		}
		for(int i = 0; i <= m; i ++ ) {
			if(i) ans[i] += ans[i - 1];
		}
		for(int i = 0; i <= m; i ++ ) cout << ans[i] << ' ';
	}
}
int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin >> n >> m;
	for(int i = 1; i <= m; i ++ ) cin >> u[i] >> v[i];
    Sol2::solve();
	return 0;
}

T3.[省选联考 2020 A 卷] 树

原题:
[省选联考 2020 A 卷] 树

题意:
给定一棵 n n n 个结点的有根树 T T T,结点从 1 1 1 开始编号,根结点为 1 1 1 号结点,每个结点有一个正整数权值 v i v_i vi

x x x 号结点的子树内(包含 x x x 自身)的所有结点编号为 c 1 , c 2 , … , c k c_1,c_2,\dots,c_k c1,c2,,ck,定义 x x x 的价值为:

v a l ( x ) = ( v c 1 + d ( c 1 , x ) ) ⊕ ( v c 2 + d ( c 2 , x ) ) ⊕ ⋯ ⊕ ( v c k + d ( c k , x ) ) val(x)=(v_{c_1}+d(c_1,x)) \oplus (v_{c_2}+d(c_2,x)) \oplus \cdots \oplus (v_{c_k}+d(c_k, x)) val(x)=(vc1+d(c1,x))(vc2+d(c2,x))(vck+d(ck,x))

其中 d ( x , y ) d(x,y) d(x,y) 表示树上 x x x 号结点与 y y y 号结点间唯一简单路径所包含的边数, d ( x , x ) = 0 d(x, x) = 0 d(x,x)=0 ⊕ \oplus 表示异或运算。

请你求出 ∑ i = 1 n v a l ( i ) \sum\limits_{i=1}^n val(i) i=1nval(i) 的结果。

对于 100 % 100\% 100% 的数据: 1 ≤ n , v i ≤ 525010 1\leq n,v_i \leq 525010 1n,vi525010 1 ≤ p i ≤ n 1\leq p_i\leq n 1pin

分析:
感觉很 t r i c k y tricky tricky,需要记住。
v a l ( x ) val(x) val(x) 实际上就是一个可重数集所有数的异或和。
发现 v a l ( x ) val(x) val(x) 中的数到求 x x x 的父亲 f a fa fa 时都加 1 1 1 了。也就是我们需要一个数据结构支持:集合中的数加 1 1 1合并两个数集求数集中所有数的异或和
如果没有第一个操作,那么我们可以用 0 / 1 t r i e 0/1 trie 0/1trie 维护,只需要类比线段树合并写一个 t r i e trie trie 树合并即可。
第一个操作也可以用 0 / 1 t r i e 0/1trie 0/1trie 维护:
正常的 0 / 1 t r i e 0/1trie 0/1trie 是从 高位到低位 插入每一个数,这样可以方便求异或第 k k k 大等信息。
但是这里我们从 低位到高位 插入每个数,那么一个 + 1 +1 +1 操作对 t r i e trie trie 有什么影响呢?
等价于 将第一层 0 / 1 0/1 0/1儿子交换,然后往原来的 1 1 1 儿子子树中递归!!!
这个可以模拟进位的过程去理解。
那么这样就可以在 log ⁡ \log log 的复杂度内完成一次数集中的数 + 1 +1 +1
总复杂度 O ( n × log ⁡ 2 V ) O(n \times \log_2V) O(n×log2V)

CODE:

// 需要支持:
// 将一个数集整体 + 1
// 合并若干数集,求数集里面所有数的异或和
// 使用 trie树,从低位到高位插入。可以支持整体 + 1 
#include<bits/stdc++.h>
#define pb emplace_back
using namespace std;
typedef long long LL;
const int N = 526010; 
int n, v[N];
int root[N], tot, tr[N * 23][2];
int num[N * 23], cnt[N * 23]; // 每个节点当前的异或和  以及经过它的数量 
int val[N], lim = 21;
vector< int > E[N];
int build() {
	tot ++;
	return tot;
}
void update(int p, int bit) {
	num[p] = (num[tr[p][0]] ^ num[tr[p][1]]);
	if(cnt[tr[p][1]] & 1) num[p] |= (1 << bit);
}
void ins(int p, int v, int bit) { // 在 p 节点插入一个值为 v 的数字 
    if(bit == lim + 1) return ;
    if((v >> bit) & 1) { // 右儿子 
     	if(!tr[p][1]) tr[p][1] = build();
     	cnt[tr[p][1]] ++;
     	ins(tr[p][1], v, bit + 1);
	}
	else {
		if(!tr[p][0]) tr[p][0] = build();
		cnt[tr[p][0]] ++;
		ins(tr[p][0], v, bit + 1);
	}
	update(p, bit);
}
int Merge(int p, int q, int bit) {
	if(!p || !q) return p ^ q;
	tr[p][0] = Merge(tr[p][0], tr[q][0], bit + 1);
	tr[p][1] = Merge(tr[p][1], tr[q][1], bit + 1);
	cnt[p] += cnt[q]; 
	update(p, bit);
	return p;
}
void Add(int p, int bit) {
	if(!p) return ;
	if(bit == lim + 1) return ;
	swap(tr[p][0], tr[p][1]); // 交换 
	Add(tr[p][0], bit + 1);
	update(p, bit);
}
void dfs(int x, int fa) {
	root[x] = build();
	ins(root[x], v[x], 0); // 从最低位开始插 
	for(auto v : E[x]) {
		if(v == fa) continue;
		dfs(v, x);
		root[x] = Merge(root[x], root[v], 0);
	}
	val[x] = num[root[x]];
	Add(root[x], 0);
}
int main() {
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++ ) scanf("%d", &v[i]);
	for(int i = 2; i <= n; i ++ ) {
		int p; scanf("%d", &p);
		E[p].pb(i); E[i].pb(p);
	}
	dfs(1, 0);
	LL res = 0;
	for(int i = 1; i <= n; i ++ ) res += 1LL * val[i];
	printf("%lld\n", res);
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/948711.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

finereport动态数据源插件教程2

场景&#xff1a; 模板中有多个数据集&#xff0c;只需要其中一个数据集按照不同的参数显示不同数据库的数据。 模板制作&#xff1a; 两个数据集ds1&#xff0c;ds2&#xff0c;ds1的绑定到参数面板的下拉框上&#xff0c;ds2显示到模板正文中&#xff0c;现在需要ds1根据不同…

Java通过谷歌邮箱Gmail直接发送邮件的三种方式

错误 Connected to the target VM, address: 127.0.0.1:52082, transport: socketException in thread "main" javax.mail.MessagingException: Got bad greeting from SMTP host: smtp.gmail.com, port: 587, response: [EOF] at com.sun.mail.smtp.SMTPTransp…

WSDM 2025 | 时间序列(time series)论文总结

AWSDM 2025于2025年3月10号到14号在德国汉诺威举行&#xff08;Hannover, Germany&#xff09; 本文总结了WSDM 2024有关时间序列&#xff08;time series&#xff09;的相关论文&#xff0c;如有疏漏&#xff0c;欢迎大家补充。&#xff08;没有时空数据相关的论文&#xff0…

反直觉导致卡关-迫击炮谜题

这个谜题&#xff0c;在两周目中先后卡了我至少三个小时&#xff0c;先后缓慢装填并发射迫击炮弹尝试了数百次。 一周目卡了很久&#xff0c;稀里糊涂的过了&#xff0c;想不到二周目还会卡那么久。 研究了很多播主的攻略&#xff0c;但还是一头雾水&#xff0c; 直到分析其…

庐山派K230学习日记4 PWM控制

1 本节介绍​ &#x1f4dd;本节您将学习如何通过将K230开发板的GPIO引脚复用为PWM功能并输出PWM信号&#xff1b;实现输出PWM信号及控制板载无源蜂鸣器发出声音。 &#x1f3c6;学习目标 1️⃣如何将GPIO引脚配置为PWM模式&#xff0c;通过40Pin排针中的部分引脚来输出PWM信号…

c语言的文件操作与文件缓冲区

目录 C语言文件操作函数汇总 简单介绍文件 为什么使用文件 什么是文件 文件名 二进制文件和文本文件 流和标准流 流 标准流 文件指针 文件的打开和关闭 文件的顺序读写 顺序读写函数介绍 文件的随机读写 fseek ftell rewind 文件读取结束的判定 文件缓冲区 缓…

嵌入式linux中socket控制与实现

一、概述 1、首先网络,一看到这个词,我们就会想到IP地址和端口号,那IP地址和端口各有什么作用呢? (1)IP地址如身份证一样,是标识的电脑的,一台电脑只有一个IP地址。 (2)端口提供了一种访问通道,服务器一般都是通过知名端口号来识别某个服务。例如,对于每个TCP/IP实…

Nginx:动静分离

什么是动静分离? 动静分离 是指将网站中的静态资源(如图片、样式表、脚本等)和动态内容(如 PHP、Python、Node.js 等后端生成的内容)分开部署和处理。这样做的好处是可以利用不同的服务器或缓存策略来优化不同类型的资源。 动静分离的好处 提高性能:静态资源可以直接从…

PADS Layout 差分线设计规则及其设计规则约束的详细过程步骤

一般我们的电路板有很多的差分线,有90欧姆的差分线,也有100欧姆的差分线,90欧姆的差分线主要是针对USB的差分线,特别是对于USB HUB的板子,那么我们就要设置差分线。一般我们设置差分线,一般要切换到Router里面来设置,如下所示: 那么设置差分对,一般要对原理图和Router…

计算机网络--路由表的更新

一、方法 【计算机网络习题-RIP路由表更新-哔哩哔哩】 二、举个例子 例1 例2

概述(讲讲python基本语法和第三方库)

我是北子&#xff0c;这是我自己写的python教程&#xff0c;主要是记录自己的学习成果方便自己日后复习&#xff0c; 我先学了C/C&#xff0c;所以这套教程中可能会将很多概念和C/C去对比&#xff0c;所以该教程大概不适合零基础的人。 it seems that python nowadays 只在人工…

redux用法总结

redux用法总结 目录 基本概念工作原理核心概念基本使用异步操作 Redux ThunkRedux Saga React 集成Redux Toolkit最佳实践 基本概念 什么是 Redux&#xff1f; Redux 是一个可预测的状态容器&#xff0c;用于管理 JavaScript 应用的状态。它遵循三个基本原则&#xff1a; …

Gitee上传项目代码教程(详细)

工具必备&#xff1a;Git Bash 上传步骤 1.在Gitee创建项目仓库 2.进入本地项目目录 右键打开Git Bash here 3.配置用户名和邮箱 如果之前给git配置过用户名和邮箱可跳过 查看Git是否配置成功&#xff1a;git config --list git config --global user.name "xxx"…

ARM CCA机密计算安全模型之安全生命周期管理

安全之安全(security)博客目录导读 目录 一、固件启用的调试 二、CCA系统安全生命周期 三、重新供应 四、可信子系统与CCA HES 启用 CCA&#xff08;机密计算架构&#xff09;的安全系统是指 CCA 平台的实现处于可信状态。 由于多种原因&#xff0c;CCA 启用系统可能处于不…

计算机视觉CV期末总复习

1.计算机视觉基础 数字图像表示 二值图像 仅包含黑白两种颜色的图像&#xff0c;只使用1个比特为&#xff08;0黑或1白&#xff09;表示 彩色图像&#xff1a;分不同的颜色空间 gray灰度图像 每个像素只有一个采样颜色&#xff0c;取值范围0--255&#xff0c;为8比特位&a…

web安全常用靶场

这里写自定义目录标题 phpstydy2018pikachuxss-labs phpstydy2018 网盘地址 提取码: nxnw ‌phpStudy是一款专为PHP开发者设计的集成环境工具&#xff0c;主要用于简化PHP开发环境的搭建过程。‌ 它集成了Apache、MySQL、PHP等核心组件&#xff0c;用户只需进行一次性安装&a…

每天40分玩转Django:Django实战 - 在线打印服务系统

Django实战 - 在线打印服务系统 一、系统功能概览表 模块主要功能技术要点文件上传PDF/Word文件上传、文件验证文件处理、MIME类型验证异步处理文件转换、打印队列Celery、Redis通知邮件打印状态通知、订单确认SMTP、邮件模板 二、系统架构设计 2.1 模型设计 # models.py …

WPS计算机二级•数据查找分析

听说这里是目录哦 通配符&#x1f30c;问号&#xff08;?&#xff09;星号&#xff08;*&#xff09;波形符&#xff08;~&#xff09; 排序&#x1f320;数字按大小排序以当前选定区域排序以扩展选定区域排序 文字按首字母排序 快速筛选分类数据☄️文字筛选数字筛选颜色筛选…

基于海思soc的智能产品开发(camera sensor的两种接口)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 对于嵌入式开发设备来说&#xff0c;除了图像显示&#xff0c;图像输入也是很重要的一部分。说到图像输入&#xff0c;就不得不提到camera。目前ca…

网安入门之MySQL后端基础

数据库 (Database) 数据库是指长期存储在计算机中的&#xff0c;有组织、可共享的数据集合。它通过表、列、行等结构来组织数据&#xff0c;目的是使数据可以高效存储、检索和管理。数据库通常包括多个表&#xff0c;每个表存储与特定主题或对象相关的数据 数据库管理系统 (D…