可持久化线段树

效果

可以追溯到任何一次修改前的状态。

可持久化线段树

  • 维护区间的可持久化线段树。
  • 维护值域的可持久化线段树(主席树)。

例题

P3919 【模板】可持久化线段树 1(可持久化数组)

板子。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 5;
const int M = log2(N) + 5;
int n, Q, a[N], v, p, c, op;

namespace Segment_Tree {
	#define mid ((L + R) >> 1)
	#define son p, L, R
	#define lson t[p].ls, L, mid
	#define rson t[p].rs, mid + 1, R
	
	int tot, rt[N];
	struct Node {
		int ls, rs, val;
	} t[N * M];
	
	inline void build(int &p, int L = 1, int R = n) {
		if(! p) p = ++ tot;
		
		if(L == R) {
			t[p].val = a[L];
			
			return ;
		}
		
		build(lson), build(rson);
		
		return ;
	}
	
	inline void add(int v, int x, int k, int &p, int L = 1, int R = n) {
		p = ++ tot;
		
		t[p] = t[v];
		
		if(L == R) {
			t[p].val = k;
			
			return ;
		}
		
		if(x <= mid) add(t[v].ls, x, k, lson);
		else add(t[v].rs, x, k, rson);
		
		return ;
	}
	
	inline int query(int x, int p, int L = 1, int R = n) {
		if(L == R) return t[p].val;
		
		if(x <= mid) return query(x, lson);
		else return query(x, rson);
	}
	
	#undef mid
	#undef son
	#undef lson
	#undef rson
}

using namespace Segment_Tree;

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> n >> Q;
	for(int i = 1 ; i <= n ; ++ i)
		cin >> a[i];
	
	build(rt[0]);
	
	for(int i = 1 ; i <= Q ; ++ i) {
		cin >> v >> op >> p;
		
		if(op == 1) {
			cin >> c;
			
			add(rt[v], p, c, rt[i]);
		}
		else {
			cout << query(p, rt[v]) << '\n';
			
			rt[i] = rt[v];
		}
	}
	
	return 0;
}

P3834 【模板】可持久化线段树 2

弱化版:假设询问全局第 \(k\) 小。
解决方法:值域线段树,维护每个值出现的次数,节点维护一段值域区间出现元素的总次数, 线段树上二分。

标准版:

  1. \(a_1, a_2, \dots , a_n\) 视为 \(n\) 次操作依次插入值域线段树,存储 \(n\) 个版本。
  2. \(root_i\) 对应的线段树插入了 \(a_1, a_2, \dots , a_i\)
  3. 当询问 \([l, r]\) 的第 \(k\) 小时,按值域线段树的逻辑寻找第 \(k\) 小,一个区间元素个数为 \(root_r\) 版本的元素个数减去 \(root_{l - 1}\) 版本的元素个数。
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 2e5 + 5;
const int V = 1e9;
const int M = log2(V) + 5;
int n, Q, l, r, k, a[N];

namespace Segment_Tree {
	#define mid ((L + R) >> 1)
	#define son p, L, R
	#define lson t[p].ls, L, mid
	#define rson t[p].rs, mid + 1, R
	
	int tot, rt[N];
	struct Node {
		int ls, rs, sum;
	} t[N * M];
	
	inline void psup(int p) {
		t[p].sum = t[t[p].ls].sum + t[t[p].rs].sum;
		
		return ;
	}
	
	inline void add(int v, int x, int k, int &p, int L = 0, int R = V) {
		p = ++ tot;
		
		t[p] = t[v];
		
		if(L == R) {
			t[p].sum += k;
			
			return ;
		}
		
		if(x <= mid) add(t[v].ls, x, k, lson);
		else add(t[v].rs, x, k, rson);
		
		psup(p);
		
		return ;
	}
	
	inline int query(int posl, int posr, int k, int L = 0, int R = V) {
		if(L == R) return L;
		
		int res = t[t[posr].ls].sum - t[t[posl].ls].sum;
		
		if(k <= res) return query(t[posl].ls, t[posr].ls, k, L, mid);
		else return query(t[posl].rs, t[posr].rs, k - res, mid + 1, R);
	}
	
	#undef mid
	#undef son
	#undef lson
	#undef rson
}

using namespace Segment_Tree;

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> n >> Q;
	for(int i = 1 ; i <= n ; ++ i)
		cin >> a[i];
	
	for(int i = 1 ; i <= n ; ++ i)
		add(rt[i - 1], a[i], 1, rt[i]);
	
	while(Q --) {
		cin >> l >> r >> k;
		
		cout << query(rt[l - 1], rt[r], k) << '\n';
	}
	
	return 0;
}

P3567 [POI 2014] KUR-Couriers

方法一:

  1. 若区间存在绝对众数,那么中位数也是众数。因此找排名为 \((R - L + 1) / 2\) 的数 \(x\)
  2. 统计 \(x\)\([L, R]\) 出现的次数。在单点 \(x\) 处用 \(R\) 版本和 \(L - 1\) 版本作差即可。

方法二:
若区间 \([L, R]\) 存在绝对众数,那么从根节点到叶子节点,路径上的每一个节点 \(R\) 版本与 \(L - 1\) 版本作差都应该有超过 $ (R - L + 1) / 2$ 的元素个数。

方法三:
摩尔投票(总统选举)。

  1. 若存在绝对众数,那么其出现次数一定超过一半。
  2. 利用众数抵消其它的数,抵完后一定有剩余。
  3. 枚举所有的众数,设 \(ans\) 为众数,\(cnt\) 为出现次数,则代码为:
for(int i = 1 ; i <= n ; ++ i) {
	if(! cnt) ans = a[i], ++ cnt;
	else if(a[i] == ans) ++ cnt;
	else -- cnt;
}
  1. 若最后 \(cnt > 0\)\(ans\) 可能是绝对众数。

CF893F Subtree Minimum Query

一个神秘 trick 是直接对于深度建出可持久化线段树。只需要在版本内查询子树最小值即可。

代码:

#include <bits/stdc++.h>
#define int long long
#define pb emplace_back
using namespace std;

const int N = 1e5 + 5;
const int INF = 1e9;
int n, Q, u, v, root, times, a[N], sz[N], dfn[N], dep[N];
vector<int> g[N], vec[N];

namespace SGT {
	#define mid ((L + R) >> 1)
	#define son p, L, R
	#define lson t[p].ls, L, mid
	#define rson t[p].rs, mid + 1, R
	
	int tot, rt[N];
	struct Node {
		int ls, rs, mn;
		Node() {
			mn = 1e18;
		}
	} t[N << 5];
	
	inline void psup(int p) {
		t[p].mn = min(t[t[p].ls].mn, t[t[p].rs].mn); 
		
		return ;
	}
	
	inline void add(int x, int k, int v, int &p, int L = 1, int R = n) {
		p = ++ tot;
		
		t[p] = t[v];
		
		if(L == R) {
			t[p].mn = k;
			
			return ;
		}
		
		if(x <= mid) add(x, k, t[v].ls, lson);
		else add(x, k, t[v].rs, rson);
		
		psup(p);
		
		return ;
	}
	
	inline int query(int l, int r, int p, int L = 1, int R = n) {
		if(! p) return 1e18;		
		
		if(l <= L && R <= r) return t[p].mn;
		
		int res = 1e18;
		
		if(l <= mid) res = min(res, query(l, r, lson));
		if(r > mid) res = min(res, query(l, r, rson));
		
		return res;
	}
	
	#undef mid
	#undef son
	#undef lson
	#undef rson 
}

using namespace SGT;

inline void dfs(int x, int last) {
	sz[x] = 1;
	dfn[x] = ++ times;
    
	for(auto u : g[x])
        if(u != last) {
            dep[u] = dep[x] + 1;
            
            dfs(u, x);
            
            sz[x] += sz[u];
        }
		
	vec[dep[x]].pb(x);
		
	return ;
}

signed main() {
    // freopen ("a.in", "r", stdin);
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> n >> root;
	for(int i = 1 ; i <= n ; ++ i)
		cin >> a[i];
	for(int i = 1 ; i < n ; ++ i) {
		cin >> u >> v;
		
		g[u].pb(v), g[v].pb(u);
	}
	
	dfs(root, -1);
	
	int maxn = 0;
	
	add(dfn[root], a[root], rt[N - 1], rt[0]);

	for(int i = 1 ; ; ++ i) {
		if(! vec[i].size()) {
			maxn = i - 1;
			
			break;
		}
		
		rt[i] = rt[i - 1];
		for(auto j : vec[i])
			add(dfn[j], a[j], rt[i], rt[i]);
	}
		
	int lst = 0;
	
	cin >> Q;

	while(Q --) {
		cin >> u >> v;
		
		u = (u + lst) % n + 1, v = (v + lst) % n;
		
		int d = min(v + dep[u], maxn);
		
//		cout << d << '\n';
		
		// cout << dfn[u] <<  " " << dfn[u] + sz[u] - 1 << endl;
		lst = query(dfn[u], dfn[u] + sz[u] - 1, rt[d]);
		
		cout << lst << '\n';
	}
	
	return 0;
}
/*
6 1
7 6 3 8 4 9
1 2
2 3
2 5
3 4
4 6
*/

CF1514D Cut and Stick

神秘结论,一眼秒了。

首先如果没有绝对众数,那么答案一定为 \(1\)

否则将所有的众数放在一个集合里面一定不劣。

于是用平衡树维护绝对众数即可。

P2468 [SDOI2010] 粟粟的书架

狗屎题。

对于 \(R, C \le 500\) 的部分大力 dp + 二分,对于线性结构的部分可持久化线段树上二分即可。

#include <bits/stdc++.h>
//#define int long long
using namespace std;

const int N = 5e5 + 5;
const int M = 205;
const int V = 1e3 + 5;
int n, m, Q, l, r, k, x, y, X, Y, a[N];
int p[M][M], sum[M][M][V], cnt[M][M][V];

namespace SGT {
	#define mid ((L + R) >> 1)
	#define son p, L, R
	#define lson t[p].ls, L, mid
	#define rson t[p].rs, mid + 1, R
	
	int tot, rt[N];
	struct Node {
		int ls, rs, sum, res;
	} t[N << 5];
	
	inline void psup(int p) {
		t[p].sum = t[t[p].ls].sum + t[t[p].rs].sum;
		t[p].res = t[t[p].ls].res + t[t[p].rs].res;
		
		return ;
	}
	
	inline void add(int x, int k, int v, int &p, int L = 0, int R = V) {
		p = ++ tot;
		
		t[p] = t[v];
		
		if(L == R) {
			t[p].sum += k;
			t[p].res += L * k;
			
			return ;
		}
		
		if(x <= mid) add(x, k, t[v].ls, lson);
		else add(x, k, t[v].rs, rson);
		
		psup(p);
		
		return ;
	}
	
	inline pair<int, int> query(int posl, int posr, int l, int r, int L = 0, int R = V) {
		if(l <= L && R <= r) return make_pair(t[posr].sum - t[posl].sum, t[posr].res - t[posl].res);
		
		pair<int, int> res = {0, 0};
		
		if(l <= mid) {
			pair<int, int> Res = query(t[posl].ls, t[posr].ls, l, r, L, mid);
			
			res = make_pair(res.first + Res.first, res.second + Res.second);
		}
		if(r > mid) {
			pair<int, int> Res = query(t[posl].rs, t[posr].rs, l, r, mid + 1, R);
			
			res = make_pair(res.first + Res.first, res.second + Res.second);
		}
		
		return res;
	}
	
	#undef mid
	#undef son
	#undef lson
	#undef rson 
}

using namespace SGT;

inline void init() {
	for(int k = 1 ; k < V ; ++ k)
		for(int i = 1 ; i <= n ; ++ i)
			for(int j = 1 ; j <= m ; ++ j) {
				cnt[i][j][k] += cnt[i - 1][j][k] + cnt[i][j - 1][k] - cnt[i - 1][j - 1][k] + (p[i][j] >= k);
				sum[i][j][k] += sum[i - 1][j][k] + sum[i][j - 1][k] - sum[i - 1][j - 1][k] + (p[i][j] >= k) * p[i][j];
			}
		
	return ;
} 

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> n >> m >> Q;
	
	if(n == 1) {
		for(int i = 1 ; i <= m ; ++ i) { 
			cin >> a[i];
			
			add(a[i], 1, rt[i - 1], rt[i]);
		}
		
		while(Q --) {
			cin >> l >> l >> r >> r >> k;
			
			int L = 1, R = V, mid = 0, ans = -1;
			pair<int, int> res;
			
			while(L <= R) {
				mid = (L + R) >> 1, res = query(rt[l - 1], rt[r], mid, V - 5);
				
				if(res.second >= k) L = mid + 1, ans = mid;
				else R = mid - 1;
			}
			
			if(ans == -1) cout << "Poor QLW\n";
			else {
				res = query(rt[l - 1], rt[r], ans, V - 5);
				
				cout << res.first - (res.second - k) / ans << '\n';
			}
		}
		
		return 0;
	}
	
	init();
	
	for(int i = 1 ; i <= n ; ++ i)
		for(int j = 1 ; j <= m ; ++ j)
			cin >> p[i][j];
			
	init();
	
	while(Q --) {
		cin >> x >> y >> X >> Y >> k;
		
		int L = 1, R = V, mid = 0, ans = -1;
		
		while(L <= R) {
			mid = (L + R) >> 1;
			
			if(sum[X][Y][mid] - sum[x - 1][Y][mid] - sum[X][y - 1][mid] + sum[x - 1][y - 1][mid] >= k) L = mid + 1,
			ans = mid;
			else R = mid - 1;
		}
		
		if(ans == -1) cout << "Poor QLW\n";
		else {
			cout << cnt[X][Y][ans] - cnt[x - 1][Y][ans] - cnt[X][y - 1][ans] + cnt[x - 1][y - 1][ans] -
			(sum[X][Y][ans] - sum[x - 1][Y][ans] - sum[X][y - 1][ans] + sum[x - 1][y - 1][ans] - k) / ans << '\n';
		}
	}
	
	return 0;
}
posted @ 2025-06-08 16:39  endswitch  阅读(21)  评论(0)    收藏  举报