可持久化线段树
效果
可以追溯到任何一次修改前的状态。
可持久化线段树
- 维护区间的可持久化线段树。
- 维护值域的可持久化线段树(主席树)。
例题
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\) 小。
解决方法:值域线段树,维护每个值出现的次数,节点维护一段值域区间出现元素的总次数, 线段树上二分。
标准版:
- 将 \(a_1, a_2, \dots , a_n\) 视为 \(n\) 次操作依次插入值域线段树,存储 \(n\) 个版本。
- \(root_i\) 对应的线段树插入了 \(a_1, a_2, \dots , a_i\)。
- 当询问 \([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
方法一:
- 若区间存在绝对众数,那么中位数也是众数。因此找排名为 \((R - L + 1) / 2\) 的数 \(x\)。
- 统计 \(x\) 在 \([L, R]\) 出现的次数。在单点 \(x\) 处用 \(R\) 版本和 \(L - 1\) 版本作差即可。
方法二:
若区间 \([L, R]\) 存在绝对众数,那么从根节点到叶子节点,路径上的每一个节点 \(R\) 版本与 \(L - 1\) 版本作差都应该有超过 $ (R - L + 1) / 2$ 的元素个数。
方法三:
摩尔投票(总统选举)。
- 若存在绝对众数,那么其出现次数一定超过一半。
- 利用众数抵消其它的数,抵完后一定有剩余。
- 枚举所有的众数,设 \(ans\) 为众数,\(cnt\) 为出现次数,则代码为:
for(int i = 1 ; i <= n ; ++ i) {
if(! cnt) ans = a[i], ++ cnt;
else if(a[i] == ans) ++ cnt;
else -- cnt;
}
- 若最后 \(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;
}