37.重链剖分
P3384 【模板】重链剖分/树链剖分
如题,已知一棵包含
N
N
N 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
1 x y z
,表示将树从 x x x 到 y y y 结点最短路径上所有节点的值都加上 z z z。2 x y
,表示求树从 x x x 到 y y y 结点最短路径上所有节点的值之和。3 x z
,表示将以 x x x 为根节点的子树内所有节点值都加上 z z z。4 x
表示求以 x x x 为根节点的子树内所有节点值之和
O ( l o g 2 n ) O(log^2n) O(log2n)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll INF = 1e18;
const ll N = 1e5 + 10;
vector<vector<int> >e(N);
int n, m, root, p;
int val[N], nval[N], top[N], tag[N << 2], sum[N << 2], sze[N << 2], f[N], siz[N], deep[N], son[N], id[N], cnt;//nval表示树DFS序新编号值
void dfs1(int x, int fa) {
deep[x] = deep[fa] + 1;
siz[x] = 1;
f[x] = fa;
for (int i : e[x]) {
if (i == fa) continue;
dfs1(i, x);
siz[x] += siz[i];
if (siz[son[x]] < siz[i]) son[x] = i;
}
}
void dfs2(int x, int topx) {
id[x] = ++cnt;//DFS序
nval[cnt] = val[x];
top[x] = topx;
if (!son[x]) return;
dfs2(son[x], topx);
for (int i : e[x]) {
if (i == son[x] || i == f[x]) continue;
dfs2(i, i);
}
}
void pushup(int x) {
sum[x] = (sum[x << 1] + sum[x << 1 | 1]) % p;
}
void pushtag(int x, int k) {
sum[x] = (sum[x] + k * sze[x]) % p;
tag[x] = (tag[x] + k) % p;
}
void pushdown(int x) {
if (tag[x]) {
pushtag(x << 1, tag[x]);
pushtag(x << 1 | 1, tag[x]);
tag[x] = 0;
}
}
void build(int x, int l, int r) {
if (l == r) {
sum[x] = nval[l] % p;
sze[x] = 1;
return;
}
int mid = l + r >> 1;
build(x << 1, l, mid);
build(x << 1 | 1, mid + 1, r);
sze[x] = sze[x << 1] + sze[x << 1 | 1];
pushup(x);
}
void treemodify(int x, int l, int r, int ql, int qr, int k) {//线段树修改
if (ql <= l && qr >= r) {
pushtag(x, k);
return;
}
pushdown(x);
int mid = l + r >> 1;
if (ql <= mid) treemodify(x << 1, l, mid, ql, qr, k);
if (qr > mid) treemodify(x << 1 | 1, mid + 1, r, ql, qr, k);
pushup(x);
}
ll treequery(int x, int l, int r, int ql, int qr) {//线段树查询
if (ql <= l && qr >= r) return sum[x] % p;
ll res = 0;
pushdown(x);
int mid = l + r >> 1;
if (ql <= mid) res = (res + treequery(x << 1, l, mid, ql, qr)) % p;
if (qr > mid) res = (res + treequery(x << 1 | 1, mid + 1, r, ql, qr)) % p;
return res;
}
void modify(int x, int y, int k) {
while (top[x] != top[y]) {
if (deep[top[x]] < deep[top[y]]) swap(x, y);
treemodify(1, 1, n, id[top[x]], id[x], k);//修改整条链
x = f[top[x]];
}
if (deep[x] > deep[y]) swap(x, y);
treemodify(1, 1, n, id[x], id[y], k);//在同一条链中
}
ll query(int x, int y) {
ll res = 0;
while (top[x] != top[y]) {
if (deep[top[x]] < deep[top[y]]) swap(x, y);
res = (res + treequery(1, 1, n, id[top[x]], id[x])) % p;//查询整条链
x = f[top[x]];
}
if (deep[x] > deep[y]) swap(x, y);
res = (res + treequery(1, 1, n, id[x], id[y])) % p;//在同一条链中
return res;
}
void solve() {
cin >> n >> m >> root >> p;
for (int i = 1; i <= n; ++i) cin >> val[i];
for (int i = 1; i < n; ++i) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs1(root, 0);
dfs2(root, root);//预处理重链
build(1, 1, n);//建立线段树
for (int i = 1; i <= m; ++i) {
int op;
cin >> op;
if (op == 1) {
int x, y, k;
cin >> x >> y >> k;
modify(x, y, k);
}
else if (op == 2) {
int x, y;
cin >> x >> y;
cout << query(x, y) << '\n';
}
else if (op == 3) {
int x, k;
cin >> x >> k;
treemodify(1, 1, n, id[x], id[x] + siz[x] - 1, k);//子树DFS序连续
}
else {
int x;
cin >> x;
cout << treequery(1, 1, n, id[x], id[x] + siz[x] - 1) << '\n';
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
边权转换为点权
38.长链剖分
P5903 【模板】树上 k 级祖先
给定一棵
n
n
n 个点的有根树。
有
q
q
q 次询问,第
i
i
i 次询问给定
x
i
,
k
i
x_i, k_i
xi,ki,要求点
x
i
x_i
xi 的
k
i
k_i
ki 级祖先,答案为
a
n
s
i
ans_i
ansi。特别地,
a
n
s
0
=
0
ans_0 = 0
ans0=0。
思路:对树进行长链剖分,记录每个点所在链的顶点和深度。
树上倍增求出每个点的
2
n
2^n
2n 级祖先。
对于每条链,如果其长度为 siz,那么在顶点处记录顶点向上的 siz 个祖先和向下的 siz 个链上的儿子。
对 i∈[1,n] 求出在二进制下的最高位
h
i
h_i
hi。
对于每次询问 x 的 k 级祖先,利用倍增数组先将 x 跳到 x 的
2
h
k
2^{h_k}
2hk 级祖先,设剩下还有 k′ 级,显然 k′<
2
h
k
2^{h_k}
2hk ,因此此时 x 所在的长链长度一定 ≥
2
h
k
2^{h_k}
2hk>k′。
由于长链长度 >k′ ,因此可以先将 x 跳到 x 所在链的顶点,若之后剩下的级数为正,则利用向上的数组求出答案,否则利用向下的数组求出答案。
预处理
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
查询
O
(
1
)
O(1)
O(1)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 5e5 + 10;
#define ui unsigned int
ui s;
inline ui get(ui x) {
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return s = x;
}
int deep[N], son[N], top[N], f[N][20], siz[N], bin[N];
vector<vector<int> > e(N), up(N), down(N);//up链顶向上siz个祖先,down向下siz个儿子
void dfs1(int x) {
deep[x] = deep[f[x][0]] + 1, siz[x] = 1;
for (int i = 1; (1 << i) <= deep[x]; ++i) f[x][i] = f[f[x][i - 1]][i - 1];
for (int i : e[x]) {
dfs1(i);
siz[x] = max(siz[x], siz[i] + 1);
if (siz[i] > siz[son[x]]) son[x] = i;
}
}
void dfs2(int x, int topx) {
top[x] = topx;
down[topx].push_back(x);//记录向下儿子
if (!son[x]) return;
dfs2(son[x], topx);
for (int i : e[x]) {
if (i == son[x]) continue;
int temp = i;
up[i].push_back(i);
for (int j = 1; j <= siz[i]; ++j) {
up[i].push_back(f[temp][0]);//记录向上祖先
temp = f[temp][0];
}
dfs2(i, i);
}
}
void solve() {
int n, q, root;
cin >> n >> q >> s;
for (int i = 1; i <= n; ++i) {
cin >> f[i][0];
if (!f[i][0]) root = i;
else e[f[i][0]].push_back(i);
bin[i] = log2(i);//[1,n]二进制下最高位
}
dfs1(root);
dfs2(root, root);//预处理长链
int temp = root;
up[root].push_back(root);
for (int i = 1; i <= siz[root]; ++i) {
up[root].push_back(f[temp][0]);
temp = f[temp][0];
}
ll last = 0, ans = 0;
for (int i = 1; i <= q; ++i) {
int x = (get(s) ^ last) % n + 1, k = (get(s) ^ last) % deep[x];
if (k == 0) {
last = x;
ans ^= (1ll * i * x);
continue;
}
int goal = deep[x] - k;
x = top[f[x][bin[k]]];//跳到x的2^bin[k]祖先所在链链顶
if (deep[x] < goal) x = down[x][goal - deep[x]];//往下找
else x = up[x][deep[x] - goal];//往上找
last = x;
ans ^= (1ll * i * x);
}
cout << ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
39.启发式合并
P3201 [HNOI2009] 梦幻布丁
n
n
n 个布丁摆成一行,进行
m
m
m 次操作。每次将某个颜色的布丁全部变成另一种颜色的,然后再询问当前一共有多少段颜色。
例如,颜色分别为
1
,
2
,
2
,
1
1,2,2,1
1,2,2,1 的四个布丁一共有
3
3
3 段颜色.
- 若 o p = 1 op = 1 op=1,则后有两个整数 x , y x, y x,y,表示将颜色 x x x 的布丁全部变成颜色 y y y。
- 若 o p = 2 op = 2 op=2,则表示一次询问。
思路:当我们要将个数较多的颜色合并到个数较少的颜色时,我们可以交换两种颜色,即改变映射关系,将颜色少的合并到颜色多的点上。
O ( n l o g n ) O(nlogn) O(nlogn)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e6 + 10;
int a[N], mp[N], ans = 0;
vector<vector<int> >e(N);
void merge(int& x, int& y) {//引用,改变映射关系
if (x == y) return;
if (e[x].size() > e[y].size()) swap(x, y);
for (int i : e[x]) {
if (a[i - 1] == y) ans--;
if (a[i + 1] == y) ans--;
}
for (int i : e[x]) a[i] = y;
e[y].insert(e[y].end(), e[x].begin(), e[x].end());
e[x].clear();
}
void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> a[i], mp[a[i]] = a[i];
e[a[i]].push_back(i);
}
for (int i = 1; i <= n; ++i) {
if (a[i - 1] != a[i]) ans++;
}
for (int i = 1; i <= m; ++i) {
int op;
cin >> op;
if (op == 1) {
int x, y;
cin >> x >> y;
merge(mp[x], mp[y]);
}
else cout << ans << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin >> _t;
while (_t--) {
solve();
}
return 0;
}
40.树上启发式合并
CF600E Lomsat gelral
有一棵
n
n
n 个结点的以
1
1
1 号结点为根的有根树。
每个结点都有一个颜色,颜色是以编号表示的,
i
i
i 号结点的颜色编号为
c
i
c_i
ci。
如果一种颜色在以
x
x
x 为根的子树内出现次数最多,称其在以
x
x
x 为根的子树中占主导地位。显然,同一子树中可能有多种颜色占主导地位。
你的任务是对于每一个
i
∈
[
1
,
n
]
i\in[1,n]
i∈[1,n],求出以
i
i
i 为根的子树中,占主导地位的颜色的编号和。
思路:对于一个点 x,首先遍历计算他所有轻儿子的贡献,并且每算完一个儿子就要清除它的所有贡献。接下来计算重儿子的贡献,并保留重儿子的贡献,然后再暴力加入 x 子树中除重儿子以外的所有点的贡献,此时即可得到答案。
随后回溯到 fa[x],如果 x 是 fa[x] 的轻儿子,那么将在 x 这一波计算下来的贡献全部清除,反之则全部保留。
O ( n l o g n ) O(nlogn) O(nlogn)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
int son[N], siz[N], color[N], cnt[N];
ll ans[N], maxn = 0, sum = 0;
vector<vector<int> >e(N);
void dfs1(int x, int father) {
siz[x] = 1;
for (int i : e[x]) {
if (i == father) continue;
dfs1(i, x);
siz[x] += siz[i];
if (siz[i] > siz[son[x]]) son[x] = i;
}
}
void modify(int x, int father, int k, int pson) {
int c = color[x];
cnt[c] += k;
if (cnt[c] > maxn) {
maxn = cnt[c];
sum = c;
}
else if (cnt[c] == maxn) sum += c;
for (int i : e[x]) {
if (i == father || i == pson) continue;//重儿子信息已保留,跳过
modify(i, x, k, pson);
}
}
void dfs2(int x, int father, int op) {
for (int i : e[x]) {
if (i == father || i == son[x]) continue;//跳过重儿子
dfs2(i, x, 0);
}
if (son[x]) dfs2(son[x], x, 1);//最后遍历重儿子
modify(x, father, 1, son[x]);
ans[x] = sum;
if (op == 0) {//清空轻儿子
modify(x, father, -1, 0);
maxn = sum = 0;
}
}
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) cin >> color[i];
for (int i = 1; i < n; ++i) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs1(1, 0);//求重儿子
dfs2(1, 0, 1);
for (int i = 1; i <= n; ++i) cout << ans[i] << " ";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin >> _t;
while (_t--) {
solve();
}
return 0;
}
41.替罪羊树
P3369 【模板】普通平衡树
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
- 插入 x x x 数
- 删除 x x x 数(若有多个相同的数,应只删除一个)
- 查询 x x x 数的排名(排名定义为比当前数小的数的个数 + 1 +1 +1 )
- 查询排名为 x x x 的数
- 求 x x x 的前驱(前驱定义为小于 x x x,且最大的数)
- 求 x x x 的后继(后继定义为大于 x x x,且最小的数)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
const double alpha = 0.7;
struct node {
int l, r, key, siz, tot, del;//tot实际节点+删除节点,siz实际节点,del=0被删除
}tr[N];
int st[N], order[N];//st用一个栈回收和分配可用节点,order记录拍平后结果
int root = 0, cnt, tot = 0;
void initnode(int x) {//重置节点参数
tr[x].l = tr[x].r = 0;
tr[x].siz = tr[x].tot = tr[x].del = 1;
}
void pushup(int x) {
tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
tr[x].tot = tr[tr[x].l].tot + tr[tr[x].r].tot + 1;
}
bool notbalance(int x) {//判断子树x是否平衡
if ((double)tr[x].siz * alpha <= (double)max(tr[tr[x].l].siz, tr[tr[x].r].siz)) return 1;
else return 0;
}
void inorder(int x) {//中序遍历拍平子树
if (!x) return;
inorder(tr[x].l);
if (tr[x].del) order[++cnt] = x;
else st[++tot] = x;
inorder(tr[x].r);
}
void build(int& x, int l, int r) {//重建
int mid = (l + r) >> 1;
x = order[mid];
if (l == r) {
initnode(x);
return;
}
if (l < mid) build(tr[x].l, l, mid - 1);
if (l == mid) tr[x].l = 0;
build(tr[x].r, mid + 1, r);
pushup(x);
}
void rebuild(int& x) {//重构
cnt = 0;
inorder(x);
if (cnt) build(x, 1, cnt);
else x = 0;
}
int rnk(int x, int k) {//求k排名
if (!x) return 0;
if (k > tr[x].key) return tr[tr[x].l].siz + tr[x].del + rnk(tr[x].r, k);
else return rnk(tr[x].l, k);
}
int kth(int k) {//排名为k的数
int x = root;
while (x) {
if (tr[x].del && tr[tr[x].l].siz + 1 == k) return tr[x].key;
else if (tr[tr[x].l].siz >= k) x = tr[x].l;
else {
k -= tr[tr[x].l].siz + tr[x].del;
x = tr[x].r;
}
}
return tr[x].key;
}
void insert(int& x, int k) {//插入数字k
if (!x) {
x = st[tot--];
tr[x].key = k;
initnode(x);
return;
}
tr[x].siz++;
tr[x].tot++;
if (tr[x].key >= k) insert(tr[x].l, k);
else insert(tr[x].r, k);
if (notbalance(x)) rebuild(x);
}
void removek(int& x, int k) {//删除排名为k的数
tr[x].siz--;
if (tr[x].del && tr[tr[x].l].siz + 1 == k) {
tr[x].del = 0;
return;
}
if (tr[tr[x].l].siz + tr[x].del >= k) removek(tr[x].l, k);
else removek(tr[x].r, k - tr[tr[x].l].siz - tr[x].del);
}
void remove(int k) {//删除值为k的数
removek(root, rnk(root, k) + 1);
if (tr[root].tot * alpha >= tr[root].siz) rebuild(root);//子树上被删除节点太多,重构
}
void solve() {
for (int i = N - 1; i >= 1; --i) st[++tot] = i;
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
int op, x;
cin >> op >> x;
if (op == 1) insert(root, x);
else if (op == 2) remove(x);
else if (op == 3) cout << rnk(root, x) + 1 << '\n';
else if (op == 4) cout << kth(x) << '\n';
else if (op == 5) cout << kth(rnk(root, x)) << '\n';
else cout << kth(rnk(root, x + 1) + 1) << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
42.Treap
P3369 【模板】普通平衡树
O
(
l
o
g
n
)
O(logn)
O(logn)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e9;
const ll N = 1e5 + 10;
struct node {
int l, r, key, val, cnt, siz;//val优先级,满足大根堆;cnt当前值个数
}tr[N];
int root, tot = 0;
int creatnode(int k) {
tr[++tot].key = k;
tr[tot].val = rand();
tr[tot].cnt = tr[tot].siz = 1;
return tot;
}
void pushup(int x) {
tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + tr[x].cnt;
}
void build() {
creatnode(-INF), creatnode(INF);//防止越界
root = 1;
tr[1].r = 2;
pushup(root);
}
void zig(int& x) {//右旋,把x左儿子旋到根
int p = tr[x].l;
tr[x].l = tr[p].r, tr[p].r = x, x = p;
pushup(tr[x].r), pushup(x);
}
void zag(int& x) {//左旋,把x右儿子旋到根
int p = tr[x].r;
tr[x].r = tr[p].l, tr[p].l = x, x = p;
pushup(tr[x].l), pushup(x);
}
void insert(int& x, int k) {
if (!x) x = creatnode(k);
else if (k == tr[x].key) tr[x].cnt++;
else if (k < tr[x].key) {
insert(tr[x].l, k);
if (tr[tr[x].l].val > tr[x].val) zig(x);
}
else {
insert(tr[x].r, k);
if (tr[tr[x].r].val > tr[x].val) zag(x);
}
pushup(x);
}
void remove(int& x, int k) {
if (!x) return;
if (k == tr[x].key) {
if (tr[x].cnt > 1) tr[x].cnt--;
else if (tr[x].l || tr[x].r) {//将x旋转到叶子节点
if (!tr[x].r || tr[tr[x].l].val > tr[tr[x].r].val) {
zig(x);
remove(tr[x].r, k);
}
else {
zag(x);
remove(tr[x].l, k);
}
}
else x = 0;
}
else if (k < tr[x].key) remove(tr[x].l, k);
else remove(tr[x].r, k);
pushup(x);
}
int rnk(int x, int k) {//求k的排名
if (!x) return 0;
if (k == tr[x].key) return tr[tr[x].l].siz + 1;
else if (k < tr[x].key) return rnk(tr[x].l, k);
else return tr[tr[x].l].siz + tr[x].cnt + rnk(tr[x].r, k);
}
int kth(int x, int k) {//排名为k的数
if (!x) return INF;
if (k <= tr[tr[x].l].siz) return kth(tr[x].l, k);
else if (k <= tr[tr[x].l].siz + tr[x].cnt) return tr[x].key;
else return kth(tr[x].r, k - tr[tr[x].l].siz - tr[x].cnt);
}
int pre(int x, int k) {
if (!x) return -INF;
if (k <= tr[x].key) return pre(tr[x].l, k);
else return max(tr[x].key, pre(tr[x].r, k));
}
int nxt(int x, int k) {
if (!x) return INF;
if (k >= tr[x].key) return nxt(tr[x].r, k);
else return min(tr[x].key, nxt(tr[x].l, k));
}
void solve() {
srand(time(0));
build();
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
int op, x;
cin >> op >> x;
if (op == 1) insert(root, x);
else if (op == 2) remove(root, x);
else if (op == 3) cout << rnk(root, x) - 1 << '\n';
else if (op == 4) cout << kth(root, x + 1) << '\n';
else if (op == 5) cout << pre(root, x) << '\n';
else cout << nxt(root, x) << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
43.FHQ Treap
普通平衡树
P3369 【模板】普通平衡树
O
(
l
o
g
n
)
O(logn)
O(logn)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct node {
int l, r, key, val, siz;
}tr[N];
int root = 0, tot = 0;
void creatnode(int k) {
tr[++tot].key = k;
tr[tot].val = rand();
tr[tot].siz = 1;
}
void pushup(int x) {
tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
}
void split(int x, int& l, int& r, int k) {//按权值k分裂成l和r两棵树
if (!x) {
l = r = 0;
return;
}
if (tr[x].key <= k) {
l = x;
split(tr[x].r, tr[x].r, r, k);
}
else {
r = x;
split(tr[x].l, l, tr[x].l, k);
}
pushup(x);
}
int merge(int x, int y) {
if (!x || !y) return x + y;
if (tr[x].val >= tr[y].val) {
tr[x].r = merge(tr[x].r, y);
pushup(x);
return x;
}
else {
tr[y].l = merge(x, tr[y].l);
pushup(y);
return y;
}
}
void insert(int k) {
int l, r, p;
split(root, l, r, k);
creatnode(k);
p = merge(l, tot);
root = merge(p, r);
}
void remove(int k) {
int l, r, p;
split(root, l, r, k);
split(l, l, p, k - 1);
p = merge(tr[p].l, tr[p].r);
root = merge(merge(l, p), r);
}
void rnk(int k) {
int l, r;
split(root, l, r, k - 1);
cout << tr[l].siz + 1 << '\n';
root = merge(l, r);
}
int kth(int x, int k) {
if (k == tr[tr[x].l].siz + 1) return tr[x].key;
else if (k <= tr[tr[x].l].siz) return kth(tr[x].l, k);
else return kth(tr[x].r, k - tr[tr[x].l].siz - 1);
}
void pre(int k) {
int l, r;
split(root, l, r, k - 1);
cout << kth(l, tr[l].siz) << '\n';
root = merge(l, r);
}
void nxt(int k) {
int l, r;
split(root, l, r, k);
cout << kth(r, 1) << '\n';
root = merge(l, r);
}
void solve() {
srand(time(0));
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
int op, x;
cin >> op >> x;
if (op == 1) insert(x);
else if (op == 2) remove(x);
else if (op == 3) rnk(x);
else if (op == 4) cout << kth(root, x) << '\n';
else if (op == 5) pre(x);
else nxt(x);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
}
排名分裂
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 3e6 + 10;
struct node {
int l, r, val, siz;
char key;
}tr[N];
int root = 0, tot = 0, pos = 0;
void creatnode(char k) {
tr[++tot].key = k;
tr[tot].val = rand();
tr[tot].siz = 1;
}
void pushup(int x) {
tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
}
void split(int x, int& l, int& r, int k) {//按排名k分裂成l和r两棵树
if (!x) {
l = r = 0;
return;
}
if (tr[tr[x].l].siz + 1 <= k) {
l = x;
split(tr[x].r, tr[x].r, r, k - tr[tr[x].l].siz - 1);
}
else {
r = x;
split(tr[x].l, l, tr[x].l, k);
}
pushup(x);
}
int merge(int x, int y) {
if (!x || !y) return x + y;
if (tr[x].val >= tr[y].val) {
tr[x].r = merge(tr[x].r, y);
pushup(x);
return x;
}
else {
tr[y].l = merge(x, tr[y].l);
pushup(y);
return y;
}
}
void insert(int len) {
int l, r;
split(root, l, r, pos);
for (int i = 1; i <= len; ++i) {
char ch = getchar();
while (ch < 32 || ch>126) ch = getchar();
creatnode(ch);
l = merge(l, tot);
}
root = merge(l, r);
}
void remove(int len) {
int l, r, p;
split(root, l, r, pos + len);
split(l, l, p, pos);
root = merge(l, r);
}
void inorder(int x) {
if (!x) return;
inorder(tr[x].l);
cout << tr[x].key;
inorder(tr[x].r);
}
void get(int len) {
int l, r, p;
split(root, l, r, pos + len);
split(l, l, p, pos);
inorder(p);
root = merge(merge(l, p), r);
}
void solve() {
srand(time(0));
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
string op;
int len;
cin >> op;
if (op[0] == 'M') cin >> pos;
else if (op[0] == 'I') {
cin >> len;
insert(len);
}
else if (op[0] == 'D') {
cin >> len;
remove(len);
}
else if (op[0] == 'G') {
cin >> len;
get(len);
cout << '\n';
}
else if (op[0] == 'P') pos--;
else pos++;
}
}
int main() {
//ios::sync_with_stdio(false);
//cin.tie(0);
//cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
区间翻转(文艺平衡树)
P3391 【模板】文艺平衡树
您需要写一种数据结构(可参考题目标题),来维护一个有序数列。
其中需要提供以下操作:翻转一个区间,例如原有序序列是
5
4
3
2
1
5\ 4\ 3\ 2\ 1
5 4 3 2 1,翻转区间是
[
2
,
4
]
[2,4]
[2,4] 的话,结果是
5
2
3
4
1
5\ 2\ 3\ 4\ 1
5 2 3 4 1。
思路:Lazy-Tag
对一个区间做翻转操作,可以以区间中任意数为轴,左右交换,然后对左、右两部分继续递归这个操作,直到结束,最后得到翻转的结果。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct node {
int l, r, key, val, siz, tag;//tag=1区间需要翻转
}tr[N];
int root = 0, tot = 0;
void creatnode(int k) {
tr[++tot].key = k;
tr[tot].val = rand();
tr[tot].siz = 1;
}
void pushup(int x) {
tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
}
void pushdown(int x) {
if (tr[x].tag) {
swap(tr[x].l, tr[x].r);
tr[tr[x].l].tag ^= 1;
tr[tr[x].r].tag ^= 1;
tr[x].tag = 0;
}
}
void split(int x, int& l, int& r, int k) {//排名分裂
if (!x) {
l = r = 0;
return;
}
pushdown(x);
if (tr[tr[x].l].siz + 1 <= k) {
l = x;
split(tr[x].r, tr[x].r, r, k - tr[tr[x].l].siz - 1);
}
else {
r = x;
split(tr[x].l, l, tr[x].l, k);
}
pushup(x);
}
int merge(int x, int y) {
if (!x || !y) return x + y;
if (tr[x].val >= tr[y].val) {
pushdown(x);
tr[x].r = merge(tr[x].r, y);
pushup(x);
return x;
}
else {
pushdown(y);
tr[y].l = merge(x, tr[y].l);
pushup(y);
return y;
}
}
void inorder(int x) {
if (!x) return;
pushdown(x);
inorder(tr[x].l);
cout << tr[x].key << " ";
inorder(tr[x].r);
}
void solve() {
srand(time(0));
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
creatnode(i);
root = merge(root, tot);
}
for (int i = 1; i <= m; ++i) {
int l, r, p, x, y;
cin >> x >> y;
split(root, l, r, y);
split(l, l, p, x - 1);
tr[p].tag ^= 1;
root = merge(merge(l, p), r);
}
inorder(root);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
可持久化平衡树
P3835 【模板】可持久化平衡树
您需要写一种数据结构(可参考题目标题),来维护一个可重整数集合,其中需要提供以下操作( 对于各个以往的历史版本 ):
- 插入 x x x
- 删除 x x x(若有多个相同的数,应只删除一个,如果没有请忽略该操作)
- 查询 x x x 的排名(排名定义为比当前数小的数的个数 + 1 +1 +1)
- 查询排名为 x x x 的数
- 求 x x x 的前驱(前驱定义为小于 x x x,且最大的数,如不存在输出 − 2 31 + 1 -2^{31}+1 −231+1 )
- 求 x x x 的后继(后继定义为大于 x x x,且最小的数,如不存在输出 2 31 − 1 2^{31}-1 231−1 )
和原本平衡树不同的一点是,每一次的任何操作都是基于某一个历史版本,同时生成一个新的版本。(操作3, 4, 5, 6即保持原版本无变化)
每个版本的编号即为操作的序号(版本0即为初始状态,空树)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 2 << 30 - 1;
const ll N = 5e5 + 10;
struct node {
int l, r, key, val, siz;
}tr[N << 7];
int root[N], tot = 0, idx = 0;
void creatnode(int k) {
tr[++tot].key = k;
tr[tot].val = rand();
tr[tot].siz = 1;
}
void pushup(int x) {
tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
}
int clone(int x) {//复制树,只需要复制树根
++tot;
tr[tot] = tr[x];
return tot;
}
void split(int x, int& l, int& r, int k) {
if (!x) {
l = r = 0;
return;
}
if (tr[x].key <= k) {
l = clone(x);//副本
split(tr[l].r, tr[l].r, r, k);
pushup(l);
}
else {
r = clone(x);
split(tr[r].l, l, tr[r].l, k);
pushup(r);
}
}
int merge(int x, int y) {
if (!x || !y) return x + y;
if (tr[x].val >= tr[y].val) {
tr[x].r = merge(tr[x].r, y);
pushup(x);
return x;
}
else {
tr[y].l = merge(x, tr[y].l);
pushup(y);
return y;
}
}
int kth(int x, int k) {
if (k <= tr[tr[x].l].siz) return kth(tr[x].l, k);
else if (k == tr[tr[x].l].siz + 1) return tr[x].key;
else return kth(tr[x].r, k - tr[tr[x].l].siz - 1);
}
void solve() {
srand(time(0));
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
int v, op, x, l, r, p;
cin >> v >> op >> x;
if (op == 1) {
split(root[v], l, r, x);
creatnode(x);
root[++idx] = merge(merge(l, tot), r);
}
else if (op == 2) {
split(root[v], l, r, x);
split(l, l, p, x - 1);
p = merge(tr[p].l, tr[p].r);
root[++idx] = merge(merge(l, p), r);
}
else if (op == 3) {
split(root[v], l, r, x - 1);
cout << tr[l].siz + 1 << '\n';
root[++idx] = merge(l, r);
}
else if (op == 4) {
cout << kth(root[v], x) << '\n';
root[++idx] = root[v];
}
else if (op == 5) {
split(root[v], l, r, x - 1);
if (!l) cout << -INF << '\n';
else cout << kth(l, tr[l].siz) << '\n';
root[++idx] = merge(l, r);
}
else {
split(root[v], l, r, x);
if (!r) cout << INF << '\n';
else cout << kth(r, 1) << '\n';
root[++idx] = merge(l, r);
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
可持久化文艺平衡树
P5055 【模板】可持久化文艺平衡树
您需要写一种数据结构,来维护一个序列,其中需要提供以下操作(对于各个以往的历史版本):
- 在第 p p p 个数后插入数 x x x 。
- 删除第 p p p 个数。
- 翻转区间 [ l , r ] [l,r] [l,r],例如原序列是 { 5 , 4 , 3 , 2 , 1 } \{5,4,3,2,1\} {5,4,3,2,1},翻转区间 [ 2 , 4 ] [2,4] [2,4] 后,结果是 { 5 , 2 , 3 , 4 , 1 } \{5,2,3,4,1\} {5,2,3,4,1}。
- 查询区间 [ l , r ] [l,r] [l,r] 中所有数的和。
和原本平衡树不同的一点是,每一次的任何操作都是基于某一个历史版本,同时生成一个新的版本(操作
4
4
4 即保持原版本无变化),新版本即编号为此次操作的序号。
本题强制在线。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 2e5 + 10;
struct node {
ll l, r, key, val, sum, siz, tag;
}tr[N << 7];
ll root[N], tot = 0, idx = 0;
void creatnode(ll k) {
++tot;
tr[tot].key = tr[tot].sum = k;
tr[tot].val = rand();
tr[tot].siz = 1;
}
ll clone(ll x) {//复制树,只需要复制树根
++tot;
tr[tot] = tr[x];
return tot;
}
void pushup(ll x) {
tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
tr[x].sum = tr[tr[x].l].sum + tr[tr[x].r].sum + tr[x].key;
}
void pushdown(ll x) {
if (tr[x].tag) {
if (tr[x].l) tr[x].l = clone(tr[x].l);//副本
if (tr[x].r) tr[x].r = clone(tr[x].r);
swap(tr[x].l, tr[x].r);
tr[tr[x].l].tag ^= 1, tr[tr[x].r].tag ^= 1;
tr[x].tag = 0;
}
}
void split(ll x, ll& l, ll& r, ll k) {
if (!x) {
l = r = 0;
return;
}
pushdown(x);
if (tr[tr[x].l].siz + 1 <= k) {
l = clone(x);//副本
split(tr[l].r, tr[l].r, r, k - tr[tr[x].l].siz - 1);
pushup(l);
}
else {
r = clone(x);
split(tr[r].l, l, tr[r].l, k);
pushup(r);
}
}
ll merge(ll x, ll y) {
if (!x || !y) return x + y;
if (tr[x].val >= tr[y].val) {
pushdown(x);
tr[x].r = merge(tr[x].r, y);
pushup(x);
return x;
}
else {
pushdown(y);
tr[y].l = merge(x, tr[y].l);
pushup(y);
return y;
}
}
void solve() {
srand(time(0));
ll n, last = 0;
cin >> n;
for (int i = 1; i <= n; ++i) {
ll v, op;
cin >> v >> op;
if (op == 1) {
ll p, x, L, R;
cin >> p >> x;
p ^= last, x ^= last;
split(root[v], L, R, p);
creatnode(x);
root[++idx] = merge(merge(L, tot), R);
}
else if (op == 2) {
ll p, L, R, tmp;
cin >> p;
p ^= last;
split(root[v], L, R, p);
split(L, L, tmp, p - 1);
root[++idx] = merge(L, R);
}
else if (op == 3) {
ll l, r, L, R, tmp;
cin >> l >> r;
l ^= last, r ^= last;
split(root[v], L, R, r);
split(L, L, tmp, l - 1);
tr[tmp].tag ^= 1;
root[++idx] = merge(merge(L, tmp), R);
}
else {
ll l, r, L, R, tmp;
cin >> l >> r;
l ^= last, r ^= last;
split(root[v], L, R, r);
split(L, L, tmp, l - 1);
last = tr[tmp].sum;
cout << last << '\n';
root[++idx] = merge(merge(L, tmp), R);
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
44.Splay
排名分裂
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 4e6 + 10;
struct node {
char key;
int l, r, fa, siz;
}tr[N];
int root = 1, tot = 2;
char s[N];
void init() {//防止越界
tr[1].siz = 2, tr[1].l = 2;
tr[2].siz = 1, tr[2].fa = 1;
}
void pushup(int x) {
tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
}
void rotate(int x) {
int y = tr[x].fa, z = tr[y].fa;
int k = (x == tr[y].r);
if (k == 1) {//x是右儿子,左旋
tr[y].r = tr[x].l;
tr[tr[x].l].fa = y;
tr[x].l = y;
}
else {//x是左儿子,右旋
tr[y].l = tr[x].r;
tr[tr[x].r].fa = y;
tr[x].r = y;
}
tr[y].fa = x;
tr[x].fa = z;
if (z) {//更新祖父的儿子
if (tr[z].l == y) tr[z].l = x;
else tr[z].r = x;
}
pushup(y), pushup(x);
}
void splay(int x, int k) {//把x旋转为k的儿子
while (tr[x].fa != k) {
int y = tr[x].fa, z = tr[y].fa;
if (z != k) {
if ((x == tr[y].r) == (y == tr[z].r)) rotate(y);//一字型
else rotate(x);//之字型
}
rotate(x);
}
if (k == 0) root = x;
}
int build(int l, int r, int f) {
if (l > r) return 0;
int mid = l + r >> 1;
int cur = ++tot;
tr[cur].fa = f;
tr[cur].key = s[mid];
tr[cur].l = build(l, mid - 1, cur);
tr[cur].r = build(mid + 1, r, cur);
pushup(cur);
return cur;
}
int kth(int x, int k) {
if (k <= tr[tr[x].l].siz) return kth(tr[x].l, k);
else if (k == tr[tr[x].l].siz + 1) return x;
else return kth(tr[x].r, k - tr[tr[x].l].siz - 1);
}
void insert(int pos, int len) {
int l = kth(root, pos), r = kth(root, pos + 1);
splay(l, 0), splay(r, l);//把l旋至根,r旋至l的儿子
tr[r].l = build(1, len, r);//建一棵树挂到左儿子
pushup(r), pushup(l);
}
void remove(int x, int len) {
int l = kth(root, x), r = kth(root, x + len + 1);
splay(l, 0), splay(r, l);
tr[r].l = 0;//剪断左子树,等于删除
pushup(r), pushup(l);
}
void inorder(int x) {
if (!x) return;
inorder(tr[x].l);
cout << tr[x].key;
inorder(tr[x].r);
}
void output(int pos, int len) {
int l = kth(root, pos), r = kth(root, pos + len + 1);
splay(l, 0), splay(r, l);
inorder(tr[r].l);
cout << '\n';
}
void solve() {
int n, len, pos = 1;
cin >> n;
init();
for (int i = 1; i <= n; ++i) {
string op;
cin >> op;
if (op[0] == 'M') cin >> pos, pos++;
else if (op[0] == 'I') {
cin >> len;
for (int i = 1; i <= len; ++i) {
char ch = getchar();
while (ch < 32 || ch>126) ch = getchar();
s[i] = ch;
}
insert(pos, len);
}
else if (op[0] == 'D') {
cin >> len;
remove(pos, len);
}
else if (op[0] == 'G') {
cin >> len;
output(pos, len);
}
else if (op[0] == 'P') pos--;
else pos++;
}
}
int main() {
//ios::sync_with_stdio(false);
//cin.tie(0);
//cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
区间翻转(文艺平衡树)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct node {
int l, r, fa, key, siz, tag;
}tr[N];
int root, tot = 0, n, m;
void creatnode(int k, int p) {
tr[++tot].key = k;
tr[tot].fa = p;
tr[tot].siz = 1;
}
void pushup(int x) {
tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
}
void pushdown(int x) {
if (tr[x].tag) {
swap(tr[x].l, tr[x].r);
tr[tr[x].l].tag ^= 1;
tr[tr[x].r].tag ^= 1;
tr[x].tag = 0;
}
}
void rotate(int x) {
int y = tr[x].fa, z = tr[y].fa;
int k = (tr[y].r == x);
if (k == 1) {
tr[y].r = tr[x].l;
tr[tr[x].l].fa = y;
tr[x].l = y;
}
else {
tr[y].l = tr[x].r;
tr[tr[x].r].fa = y;
tr[x].r = y;
}
tr[y].fa = x;
tr[x].fa = z;
if (tr[z].l == y) tr[z].l = x;
else tr[z].r = x;
pushup(y), pushup(x);
}
void splay(int x, int k) {
while (tr[x].fa != k) {
int y = tr[x].fa, z = tr[y].fa;
if (z != k) {
if ((tr[y].r == x) == (tr[z].r == y)) rotate(y);
else rotate(x);
}
rotate(x);
}
if (k == 0) root = x;
}
void insert(int k) {
int x = root, p = 0;
while (x) {
p = x;
if (tr[x].key < k) x = tr[x].r;
else x = tr[x].l;
}
creatnode(k, p);
if (p) {
if (k > tr[p].key) tr[p].r = tot;
else tr[p].l = tot;
}
splay(tot, 0);
}
int kth(int x, int k) {
pushdown(x);
if (k <= tr[tr[x].l].siz) return kth(tr[x].l, k);
else if (k == tr[tr[x].l].siz + 1) return x;
else return kth(tr[x].r, k - tr[tr[x].l].siz - 1);
}
void inorder(int x) {
if (!x) return;
pushdown(x);
inorder(tr[x].l);
if (tr[x].key >= 1 && tr[x].key <= n) cout << tr[x].key << " ";
inorder(tr[x].r);
}
void solve() {
cin >> n >> m;
for (int i = 0; i <= n + 1; ++i) insert(i);
for (int i = 1; i <= m; ++i) {
int l, r;
cin >> l >> r;
l = kth(root, l), r = kth(root, r + 2);
splay(l, 0), splay(r, l);
tr[tr[r].l].tag ^= 1;
}
inorder(root);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
分裂、合并、删除节点操作
45.笛卡尔树
P5854 【模板】笛卡尔树
给定一个
1
∼
n
1 \sim n
1∼n 的排列
p
p
p,构建其笛卡尔树。
即构建一棵二叉树,满足:
- 每个节点的编号满足二叉搜索树的性质。
- 节点 i i i 的权值为 p i p_i pi,每个节点的权值满足小根堆的性质。
建树 O ( n ) O(n) O(n)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e9;
const ll N = 1e7 + 10;
struct node {
int l, r, val, fa;
}tr[N];
int read() {
int res = 0;
char ch = getchar();
while (ch < '0' || ch>'9') ch = getchar();
while (ch >= '0' && ch <= '9') { res = (res << 3) + (res << 1) + (ch ^ 48); ch = getchar(); }
return res;
}
void build(int n) {
for (int i = 1; i <= n; ++i) {
int p = i - 1;
while (tr[i].val < tr[p].val) p = tr[p].fa;//沿最右链一直找
tr[i].l = tr[p].r;
tr[tr[p].r].fa = i;
tr[p].r = i;
tr[i].fa = p;
}
}
void solve() {
ll n, ans1 = 0, ans2 = 0;
n = read();
for (int i = 1; i <= n; ++i) tr[i].val = read();
tr[0].val = -INF;
build(n);
for (int i = 1; i <= n; ++i) {
ans1 ^= 1ll * i * (tr[i].l + 1);
ans2 ^= 1ll * i * (tr[i].r + 1);
}
cout << ans1 << " " << ans2;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
RMQ为区间端点LCA的值
46.线段树套平衡树
P3380 【模板】二逼平衡树(树套树)
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:
- 查询 k k k 在区间内的排名
- 查询区间内排名为 k k k 的值
- 修改某一位置上的数值
- 查询
k
k
k 在区间内的前驱(前驱定义为严格小于
x
x
x,且最大的数,若不存在输出
-2147483647
) - 查询
k
k
k 在区间内的后继(后继定义为严格大于
x
x
x,且最小的数,若不存在输出
2147483647
)
思路:用线段树划分序列,每个节点开一棵平衡树,用来维护该段区间元素信息
排名为k的值
O
(
l
o
g
3
n
)
O(log^3n)
O(log3n)
其余
O
(
l
o
g
2
n
)
O(log^2n)
O(log2n)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 2147483647;
const ll N = 2e6 + 10;
struct tree {
int l, r, siz, key, val;
}tr[N];
int a[N], root[N], tot = 0;
inline int read() {
int res = 0;
char ch = getchar();
while (ch < '0' || ch>'9') ch = getchar();
while (ch >= '0' && ch <= '9') { res = (res << 3) + (res << 1) + (ch ^ 48); ch = getchar(); }
return res;
}
inline void pushup(int x) {
tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
}
inline void creatnode(int k) {
tr[++tot].key = k;
tr[tot].val = rand();
tr[tot].siz = 1;
}
inline void split(int x, int& l, int& r, int k) {
if (!x) {
l = r = 0;
return;
}
if (tr[x].key <= k) {
l = x;
split(tr[x].r, tr[x].r, r, k);
}
else {
r = x;
split(tr[x].l, l, tr[x].l, k);
}
pushup(x);
}
inline int merge(int x, int y) {
if (!x || !y) return x + y;
if (tr[x].val >= tr[y].val) {
tr[x].r = merge(tr[x].r, y);
pushup(x);
return x;
}
else {
tr[y].l = merge(x, tr[y].l);
pushup(y);
return y;
}
}
inline void insert(int& rt, int k) {
int l, r;
split(rt, l, r, k);
creatnode(k);
rt = merge(merge(l, tot), r);
}
inline void remove(int& rt, int k) {
int l, r, p;
split(rt, l, r, k);
split(l, l, p, k - 1);
p = merge(tr[p].l, tr[p].r);
rt = merge(merge(l, p), r);
}
inline int rnk(int& rt, int k) {//查询小于k数量,即k的排名-1,
int u = rt, res = 0;
while (u) {
if (tr[u].key < k) res += tr[tr[u].l].siz + 1, u = tr[u].r;
else u = tr[u].l;
}
return res;
}
inline int kth(int x, int k) {//查询排名为k的值
if (k == tr[tr[x].l].siz + 1) return tr[x].key;
else if (k <= tr[tr[x].l].siz) return kth(tr[x].l, k);
else return kth(tr[x].r, k - tr[tr[x].l].siz - 1);
}
inline int pre(int& rt, int k) {
int l, r;
split(rt, l, r, k - 1);
int res = kth(l, tr[l].siz);
rt = merge(l, r);
return res;
}
inline int nxt(int& rt, int k) {
int l, r;
split(rt, l, r, k);
int res = kth(r, 1);
rt = merge(l, r);
return res;
}
inline void build(int x, int l, int r) {
insert(root[x], -INF), insert(root[x], INF);
for (register int i = l; i <= r; ++i) insert(root[x], a[i]);
int mid = l + r >> 1;
if (l == r) return;
build(x << 1, l, mid);
build(x << 1 | 1, mid + 1, r);
}
inline void modify(int x, int l, int r, int p, int k) {//线段树修改
remove(root[x], a[p]);
insert(root[x], k);
if (l == r) return;
int mid = l + r >> 1;
if (p <= mid) modify(x << 1, l, mid, p, k);
else modify(x << 1 | 1, mid + 1, r, p, k);
}
inline int query(int x, int l, int r, int ql, int qr, int k) {//线段树查询区间k排名
if (ql <= l && qr >= r) return rnk(root[x], k) - 1;
int res = 0;
int mid = l + r >> 1;
if (ql <= mid) res += query(x << 1, l, mid, ql, qr, k);
if (qr > mid) res += query(x << 1 | 1, mid + 1, r, ql, qr, k);
return res;
}
inline int querypre(int x, int l, int r, int ql, int qr, int k) {//线段树查询区间前驱
if (ql <= l && qr >= r) return pre(root[x], k);
int res = -INF;
int mid = l + r >> 1;
if (ql <= mid) res = max(res, querypre(x << 1, l, mid, ql, qr, k));
if (qr > mid) res = max(res, querypre(x << 1 | 1, mid + 1, r, ql, qr, k));
return res;
}
inline int querynxt(int x, int l, int r, int ql, int qr, int k) {//线段树查询区间后继
if (ql <= l && qr >= r) return nxt(root[x], k);
int res = INF;
int mid = l + r >> 1;
if (ql <= mid) res = min(res, querynxt(x << 1, l, mid, ql, qr, k));
if (qr > mid) res = min(res, querynxt(x << 1 | 1, mid + 1, r, ql, qr, k));
return res;
}
inline void solve() {
srand(time(0));
int n, m;
n = read(), m = read();
for (register int i = 1; i <= n; ++i) a[i] = read();
build(1, 1, n);
for (int i = 1; i <= m; ++i) {
int op, k;
op = read();
if (op == 1) {
int l, r;
l = read(), r = read(), k = read();
cout << query(1, 1, n, l, r, k) + 1 << '\n';
}
else if (op == 2) {
int l, r;
l = read(), r = read(), k = read();
int L = 1, R = 1e8;
while (L <= R) {//二分
int mid = L + R >> 1;
if (query(1, 1, n, l, r, mid) + 1 <= k) L = mid + 1;
else R = mid - 1;
}
cout << R << '\n';
}
else if (op == 3) {
int pos;
pos = read(), k = read();
modify(1, 1, n, pos, k);
a[pos] = k;
}
else if (op == 4) {
int l, r;
l = read(), r = read(), k = read();
cout << querypre(1, 1, n, l, r, k) << '\n';
}
else {
int l, r;
l = read(), r = read(), k = read();
cout << querynxt(1, 1, n, l, r, k) << '\n';
}
}
}
int main() {
//ios::sync_with_stdio(false);
//cin.tie(0);
//cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
47.树状数组套线段树
P3157 [CQOI2011] 动态逆序对
对于序列
a
a
a,它的逆序对数定义为集合
{
(
i
,
j
)
∣
i
<
j
∧
a
i
>
a
j
}
\{(i,j)| i<j \wedge a_i > a_j \}
{(i,j)∣i<j∧ai>aj}中的元素个数。
现在给出
1
∼
n
1\sim n
1∼n 的一个排列,按照某种顺序依次删除
m
m
m 个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。
1
≤
n
≤
1
0
5
1\le n \le 10^5
1≤n≤105,
1
≤
m
≤
50000
1\le m \le 50000
1≤m≤50000。
在线
时间
O
(
n
log
2
n
)
O(n\log^2 n)
O(nlog2n)
空间
O
(
n
log
2
n
)
O(n\log^2 n)
O(nlog2n)
动态逆序对还有CDQ分治离线做法
见博客
48.KD树
对k维空间进行划分的二叉树,二维时按x和y中值进行交替二分和建树
最近邻点
hdu2966 In case of failure
为了帮助客户处理出现故障的自动取款机,平面银行董事会决定在每台自动取款机上都贴上“本行对故障深表歉意”的标签。同样的标签会温和地要求顾客冷静地走向最近的机器(希望这样可以。
工作好)。
为了做到这一点,已经准备好了所有n个atm机的二维位置列表,您的任务是为每个atm机找到一个相对于欧几里得距离最近的位置。
O ( l o g n ) ∼ O ( n ) O(logn)\sim O(n) O(logn)∼O(n)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
const int k = 2;
struct Point {
ll dim[k];
}p[N], tr[N];//tr存储二叉树
ll ans, cur;
bool cmp(Point a, Point b) {
return a.dim[cur] < b.dim[cur];
}
ll sq(ll x) {
return x * x;
}
ll dis(Point a, Point b) {
return sq(a.dim[0] - b.dim[0]) + sq(a.dim[1] - b.dim[1]);
}
void build(int l, int r, int dep) {
if (l >= r) return;
int mid = l + r >> 1;
cur = dep;
nth_element(tr + l, tr + mid, tr + r, cmp);//将中值元素放在tr数组中适当位置,其他元素按照中值大小划分。
build(l, mid, (dep + 1) % k);
build(mid + 1, r, (dep + 1) % k);
}
void query(int l, int r, int dep, Point a) {//查找p的最近点
if (l >= r) return;
int mid = l + r >> 1;
ll d = dis(tr[mid], a);
if (!ans || d && ans > d) ans = d;//特判tr[mid]与p重合
if (a.dim[dep] > tr[mid].dim[dep]) {//p该维大于子树的根,查右子树
query(mid + 1, r, (dep + 1) % k, a);
if (ans > sq(a.dim[dep] - tr[mid].dim[dep]))//若以ans为半径圆与左子树相交,那么左子树也要查
query(l, mid, (dep + 1) % k, a);
}
else {
query(l, mid, (dep + 1) % k, a);
if (ans > sq(a.dim[dep] - tr[mid].dim[dep]))
query(mid + 1, r, (dep + 1) % k, a);
}
}
void solve() {
int n;
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> p[i].dim[0] >> p[i].dim[1];
tr[i] = p[i];
}
build(0, n, 0);
for (int i = 0; i < n; ++i) {
ans = 0;
query(0, n, 0, p[i]);
cout << ans << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t;
//_t = 1;
cin >> _t;
while (_t--) {
solve();
}
return 0;
}
区间查询
P4148 简单题
你有一个
N
×
N
N \times N
N×N的棋盘,每个格子内有一个整数,初始时的时候全部为
0
0
0,现在需要维护两种操作:
1 x y A
1 ≤ x , y ≤ N 1\le x,y\le N 1≤x,y≤N, A A A 是正整数。将格子x
,y
里的数字加上 A A A。2 x1 y1 x2 y2
1 ≤ x 1 ≤ x 2 ≤ N 1 \le x_1 \le x_2 \le N 1≤x1≤x2≤N, 1 ≤ y 1 ≤ y 2 ≤ N 1 \le y_1\le y_2 \le N 1≤y1≤y2≤N。输出 x 1 , y 1 , x 2 , y 2 x_1, y_1, x_2, y_2 x1,y1,x2,y2 这个矩形内的数字和3
无 终止程序
思路:KD树把二维空间分为一个个小矩形,转换成二叉树,每个小矩形对应一棵子树。只要统计这个矩形范围包含了哪些小矩形,计算这些小矩形的数字和即可。
O ( l o g n ) O(logn) O(logn)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 2e5 + 10;
const double alpha = 0.75;
struct point {
int dim[2], k;
point() {}
point(int x, int y, int key) {
dim[0] = x;
dim[1] = y;
k = key;
}
}order[N];
struct node {
int l, r, sum, siz, mn[2], mx[2];
point p;
}tr[N];
int root, tot, cur, top, st[N], cnt;
bool cmp(point a, point b) {
return a.dim[cur] < b.dim[cur];
}
void pushup(int u) {
for (int i = 0; i < 2; ++i) {
tr[u].mn[i] = tr[u].mx[i] = tr[u].p.dim[i];
if (tr[u].l) {
tr[u].mn[i] = min(tr[u].mn[i], tr[tr[u].l].mn[i]);
tr[u].mx[i] = max(tr[u].mx[i], tr[tr[u].l].mx[i]);
}
if (tr[u].r) {
tr[u].mn[i] = min(tr[u].mn[i], tr[tr[u].r].mn[i]);
tr[u].mx[i] = max(tr[u].mx[i], tr[tr[u].r].mx[i]);
}
}
tr[u].sum = tr[tr[u].l].sum + tr[tr[u].r].sum + tr[u].p.k;
tr[u].siz = tr[tr[u].l].siz + tr[tr[u].r].siz + 1;
}
void slap(int u) {//替罪羊树拍平
if (!u) return;
slap(tr[u].l);
order[++cnt] = tr[u].p;
st[++top] = u;
slap(tr[u].r);
}
int build(int l, int r, int d) {//替罪羊树重建
if (l > r) return 0;
int u;
if (top) u = st[top--];
else u = ++tot;
int mid = l + r >> 1;
cur = d;
nth_element(order + l, order + mid, order + r + 1, cmp);
tr[u].p = order[mid];
tr[u].l = build(l, mid - 1, d ^ 1);
tr[u].r = build(mid + 1, r, d ^ 1);
pushup(u);
return u;
}
bool notbalance(int u) {//判断子树是否平衡
if (tr[tr[u].l].siz > alpha * tr[u].siz || tr[tr[u].r].siz > alpha * tr[u].siz) return 1;
else return 0;
}
void insert(int& u, point x, int d) {
if (!u) {
if (top) u = st[top--];
else u = ++tot;
tr[u].l = tr[u].r = 0;
tr[u].p = x;
pushup(u);
return;
}
if (x.dim[d] <= tr[u].p.dim[d]) insert(tr[u].l, x, d ^ 1);
else insert(tr[u].r, x, d ^ 1);
pushup(u);
if (notbalance(u)) {
cnt = 0;
slap(u);
u = build(1, tr[u].siz, d);
}
}
int query(int u, int x1, int y1, int x2, int y2) {
if (!u) return 0;
int xx1 = tr[u].mn[0], yy1 = tr[u].mn[1], xx2 = tr[u].mx[0], yy2 = tr[u].mx[1];
if (x1 <= xx1 && x2 >= xx2 && y1 <= yy1 && y2 >= yy2) return tr[u].sum;//子树表示矩形完全在查询范围内
if (x1 > xx2 || x2<xx1 || y1>yy2 || y2 < yy1) return 0;//子树表示矩形完全在查询范围外
int ans = 0;
xx1 = tr[u].p.dim[0], yy1 = tr[u].p.dim[1], xx2 = tr[u].p.dim[0], yy2 = tr[u].p.dim[1];
if (x1 <= xx1 && x2 >= xx2 && y1 <= yy1 && y2 >= yy2) ans += tr[u].p.k;//根在查询矩阵内
ans += query(tr[u].l, x1, y1, x2, y2) + query(tr[u].r, x1, y1, x2, y2);//递归左右子树
return ans;
}
void solve() {
int n, ans = 0;
cin >> n;
while (1) {
int op;
cin >> op;
if (op == 1) {
int x, y, k;
cin >> x >> y >> k;
x ^= ans, y ^= ans, k ^= ans;
insert(root, point(x, y, k), 0);
}
else if (op == 2) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
x1 ^= ans, y1 ^= ans, x2 ^= ans, y2 ^= ans;
ans = query(root, x1, y1, x2, y2);
cout << ans << '\n';
}
else break;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
K近邻
P2093 [国家集训队] JZPFAR
平面上有
n
n
n 个点。现在有
m
m
m 次询问,每次给定一个点
(
p
x
,
p
y
)
(px, py)
(px,py) 和一个整数
k
k
k,输出
n
n
n 个点中离
(
p
x
,
p
y
)
(px, py)
(px,py) 的距离第
k
k
k 大的点的标号。如果有两个(或多个)点距离
(
p
x
,
p
y
)
(px, py)
(px,py) 相同,那么认为标号较小的点距离较大。
思路:用优先队列动态维护小根堆,保持k个最优点,最后输出堆顶元素的编号即可
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct point {
ll dim[2], id;
}p[N];
struct tree {
ll l, r, mx[2], mn[2], id;
point p;
}tr[N];
struct node {
ll dis, id;
bool operator<(const node a)const {
return dis == a.dis ? id < a.id : dis > a.dis;
}
};
priority_queue<node> q;
int root, tot, now;
bool cmp(point a, point b) {
return a.dim[now] < b.dim[now];
}
void pushup(int x) {
for (int i = 0; i < 2; ++i) {
tr[x].mn[i] = tr[x].mx[i] = tr[x].p.dim[i];
if (tr[x].l) {
tr[x].mn[i] = min(tr[x].mn[i], tr[tr[x].l].mn[i]);
tr[x].mx[i] = max(tr[x].mx[i], tr[tr[x].l].mx[i]);
}
if (tr[x].r) {
tr[x].mn[i] = min(tr[x].mn[i], tr[tr[x].r].mn[i]);
tr[x].mx[i] = max(tr[x].mx[i], tr[tr[x].r].mx[i]);
}
}
}
int build(int l, int r, int d) {
if (l > r) return 0;
int x = ++tot;
int mid = l + r >> 1;
now = d;
nth_element(p + l, p + mid, p + r + 1, cmp);
tr[x].p = p[mid];
tr[x].id = p[mid].id;
tr[x].l = build(l, mid - 1, d ^ 1);
tr[x].r = build(mid + 1, r, d ^ 1);
pushup(x);
return x;
}
ll sq(ll x) {
return x * x;
}
ll dis(point a, point b) {
return sq(a.dim[0] - b.dim[0]) + sq(a.dim[1] - b.dim[1]);
}
ll getdis(int x, point a) {
ll res = 0;
for (int i = 0; i < 2; ++i) {
res += max(sq(a.dim[i] - tr[x].mx[i]), sq(a.dim[i] - tr[x].mn[i]));
}
return res;
}
void query(int x, point a) {
if (!x) return;
ll res = dis(tr[x].p, a);
if (res > q.top().dis || res == q.top().dis && tr[x].id < q.top().id) {//找到更优点
q.pop();
q.push({ res,tr[x].id });
}
ll ld = INF, rd = INF;
if (tr[x].l) ld = getdis(tr[x].l, a);
if (tr[x].r) rd = getdis(tr[x].r, a);
if (ld < rd) {//右边远,先查右边
if (rd >= q.top().dis) query(tr[x].r, a);
if (ld >= q.top().dis) query(tr[x].l, a);
}
else {
if (ld > q.top().dis) query(tr[x].l, a);
if (rd > q.top().dis) query(tr[x].r, a);
}
}
void solve() {
int n, m, k;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> p[i].dim[0] >> p[i].dim[1];
p[i].id = i;
}
root = build(1, n, 0);
cin >> m;
for (int i = 1; i <= m; ++i) {
point a;
cin >> a.dim[0] >> a.dim[1] >> k;
while (!q.empty()) q.pop();
for (int j = 1; j <= k; ++j) q.push({ -1,0 });
query(root, a);
cout << q.top().id << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin >> _t;
while (_t--) {
solve();
}
return 0;
}
平面最近邻居问题。已知平面上n个点的坐标,且每个点有一个权值,做m次查询,每次查询给出一个坐标点u和一个k值,求距离u最近的权值小于k的点。
平面上n个点,两点之间的距离定义为曼哈顿距离。有m次查询,每次查询给定两个点,问两点的距离有多远。
有n个四维空间中的点,求出一条最长的路径,满足任意维坐标都是单调不降的。
49.LCT
将原树剖分成实链和虚边。实链是一条从上到下的路径,实链间通过虚边连接。将每条实链按节点深度构造成一棵splay树,链顶对应splay树最左节点,splay 根节点的父亲为该实链顶端节点的父亲
P3690 【模板】动态树(LCT)
给定
n
n
n 个点以及每个点的权值,要你处理接下来的
m
m
m 个操作。
操作有四种,操作从
0
0
0 到
3
3
3 编号。点从
1
1
1 到
n
n
n 编号。
0 x y
代表询问从 x x x 到 y y y 的路径上的点的权值的 xor \text{xor} xor 和。保证 x x x 到 y y y 是联通的。1 x y
代表连接 x x x 到 y y y,若 x x x 到 y y y 已经联通则无需连接。2 x y
代表删除边 ( x , y ) (x,y) (x,y),不保证边 ( x , y ) (x,y) (x,y) 存在。3 x y
代表将点 x x x 上的权值变成 y y y。
O ( l o g n ) O(logn) O(logn)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 998244353;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct node {
int l, r,fa, key, tag, sum;
}tr[N];
int st[N];
void pushup(int x) {
tr[x].sum = tr[tr[x].l].sum ^ tr[tr[x].r].sum ^ tr[x].key;
}
void pushtag(int x) {//翻转
swap(tr[x].l, tr[x].r);
tr[x].tag ^= 1;
}
void pushdown(int x) {
if (tr[x].tag) {
pushtag(tr[x].l);
pushtag(tr[x].r);
tr[x].tag = 0;
}
}
bool isroot(int x) {//判断是否为splay树根
return tr[tr[x].fa].l != x && tr[tr[x].fa].r != x;
}
void rotate(int x) {
int y = tr[x].fa, z = tr[y].fa;
int k = (x == tr[y].r);
if (!isroot(y)) {
if (tr[z].l == y) tr[z].l = x;
else tr[z].r = x;
}
if (k == 1) {
tr[y].r = tr[x].l;
tr[tr[x].l].fa = y;
tr[x].l = y;
}
else {
tr[y].l = tr[x].r;
tr[tr[x].r].fa = y;
tr[x].r = y;
}
tr[y].fa = x;
tr[x].fa = z;
pushup(y), pushup(x);
}
void splay(int x) {//把x旋至splay树的根
int top = 0, p = x;
st[++top] = p;
while (!isroot(p)) st[++top] = p = tr[p].fa;
while (top) pushdown(st[top--]);
while (!isroot(x)) {
int y = tr[x].fa, z = tr[y].fa;
if (!isroot(y)) {
if ((tr[y].r == x) == (tr[z].r == y)) rotate(y);
else rotate(x);
}
rotate(x);
}
}
void access(int x) {//把点x到根的路径“打通”,就是把点x到根划到同一棵splay中,在连接的同时把其它链断开。
int z = x;
for (int y = 0; x; y = x, x = tr[x].fa){//从x沿虚边走到根
splay(x);
tr[x].r = y;//建立实边
pushup(x);
}
splay(z);
}
void makeroot(int x) {//将x变为原树的根
access(x);
pushtag(x);//翻转splay左右,即颠倒原树该实链上下
}
int findroot(int x) {//查询x在原树上的根
access(x);
while (tr[x].l) x = tr[x].l;
splay(x);
return x;
}
void split(int x, int y) {//在原树上以x为起点,y为终点生成一条实链
makeroot(x);
access(y);
}
void link(int x, int y) {//在x和y间连一条边
makeroot(x);
if (findroot(y) != x) tr[x].fa = y;
}
void cut(int x, int y) {//断开x和y间边
makeroot(x);
if (findroot(y) == x && tr[y].fa == x && !tr[y].l) {
tr[x].r = tr[y].fa = 0;
pushup(x);
}
}
void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i) cin >> tr[i].key;
for (int i = 1; i <= m; ++i) {
int op, x, y;
cin >> op >> x >> y;
if (op == 0) {
split(x, y);
cout << tr[y].sum << '\n';
}
else if (op == 1) link(x, y);
else if (op == 2) cut(x, y);
else {
splay(x);
tr[x].key = y;
pushup(x);
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin >> _t;
while (_t--) {
solve();
}
return 0;
}
判断连通性
求两点间距离
求LCA
50.左偏树
对于一棵二叉树,定义外节点为左儿子或右儿子为空的节点,定义一个外节点的 dist 为 1,一个不是外节点的节点 dist 为其到子树中最近的外节点的距离加一。
左偏树是一棵二叉树,它不仅具有堆的性质,并且是「左偏」的:每个节点左儿子的 dist 都大于等于右儿子的 dist。
因此,左偏树每个节点的 dist 都等于其右儿子的 dist 加一。
P3377 【模板】左偏树(可并堆)
如题,一开始有
n
n
n 个小根堆,每个堆包含且仅包含一个数。接下来需要支持两种操作:
1 x y
:将第 x x x 个数和第 y y y 个数所在的小根堆合并(若第 x x x 或第 y y y 个数已经被删除或第 x x x 和第 y y y 个数在用一个堆内,则无视此操作)。2 x
:输出第 x x x 个数所在的堆最小数,并将这个最小数删除(若有多个最小数,优先删除先输入的;若第 x x x 个数已经被删除,则输出 − 1 -1 −1 并无视删除操作)。
O ( l o g n ) O(logn) O(logn)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct {
int l, r, key, dis;
bool del;//是否已被删除
}tr[N];
int f[N];//并查集
bool cmp(int x, int y) {
if (tr[x].key != tr[y].key) return tr[x].key < tr[y].key;
else return x < y;
}
int find(int x) {
if (f[x] == x) return x;
else return f[x] = find(f[x]);
}
int merge(int x, int y) {
if (!x || !y) return x + y;
if (!cmp(x, y)) swap(x, y);//保证值x<y
tr[x].r = merge(tr[x].r, y);//x为根(最小值),继续合并x右子树
if (tr[tr[x].l].dis < tr[tr[x].r].dis) swap(tr[x].l, tr[x].r);//保证左偏
tr[x].dis = tr[tr[x].r].dis + 1;
return x;
}
void solve() {
int n, m;
cin >> n >> m;
tr[0].key = 2e9;
for (int i = 1; i <= n; ++i) {
cin >> tr[i].key;
tr[i].dis = 1;
f[i] = i;
}
for (int i = 1; i <= m; ++i) {
int op;
cin >> op;
if (op == 1) {
int x, y;
cin >> x >> y;
if (tr[x].del || tr[y].del) continue;
x = find(x), y = find(y);
if (x != y) {
if (!cmp(x, y)) swap(x, y);//保证值x<y
merge(x, y);
f[y] = x;
}
}
else {
int x;
cin >> x;
if (tr[x].del) {
cout << -1 << '\n';
continue;
}
x = find(x);
cout << tr[x].key << '\n';
if (!cmp(tr[x].l, tr[x].r)) swap(tr[x].l, tr[x].r);//保证值tr[x].l<tr[x].r
merge(tr[x].l, tr[x].r);//tr[x].l为新根(最小值)
tr[x].del = 1;
f[x] = tr[x].l;//并查集换根
f[tr[x].l] = tr[x].l;
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
51.DLX
精确覆盖问题
AcWing1067精确覆盖问题
给定一个 N×M 的数字矩阵 A,矩阵中的元素
A
i
,
j
A_{i,j}
Ai,j∈{0,1}。
请问,你能否在矩阵中找到一个行的集合,使得这些行中,每一列都有且仅有一个数字 1。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 6e3 + 10;
struct node {
int l, r, u, d, row, col;//lrud左右上下下一个1位置,row,col该点所在行,列
}p[N];
int n, m, cnt[N], tot, ans[N], top;//cnt该列1的个数
void init() {
for (int i = 0; i <= m; ++i) {
p[i].l = i - 1, p[i].r = i + 1;
p[i].u = p[i].d = i;
}
p[0].l = m, p[m].r = 0;
tot = m + 1;
}
void add(int& hh, int& tt, int x, int y) {
p[tot].row = y, p[tot].col = x, cnt[x]++;
p[tot].l = hh, p[tot].r = tt;
p[hh].r = p[tt].l = tot;
p[tot].u = x, p[tot].d = p[x].d;
p[x].d = p[p[x].d].u = tot;
tt = tot++;
}
void remove(int x) {//删除第x列及该列为1的每一行
p[p[x].l].r = p[x].r, p[p[x].r].l = p[x].l;
for (int i = p[x].d; i != x; i = p[i].d) {
for (int j = p[i].r; j != i; j = p[j].r) {
cnt[p[j].col]--;
p[p[j].d].u = p[j].u, p[p[j].u].d = p[j].d;
}
}
}
void resume(int x) {//恢复第x列及相关行
for (int i = p[x].u; i != x; i = p[i].u) {
for (int j = p[i].l; j != i; j = p[j].l) {
cnt[p[j].col]++;
p[p[j].d].u = j, p[p[j].u].d = j;
}
}
p[p[x].l].r = x, p[p[x].r].l = x;
}
bool dfs() {
if (!p[0].r) return 1;//矩阵为空,满足
int mn = p[0].r;
for (int i = p[0].r; i; i = p[i].r) {//找1个数最少的列
if (cnt[i] < cnt[mn]) mn = i;
}
remove(mn);//删除改列
for (int i = p[mn].d; i != mn; i = p[i].d) {//遍历mn所有为1的行
ans[++top] = p[i].row;//假定选择该行
for (int j = p[i].r; j != i; j = p[j].r) remove(p[j].col);//删除这一行中为1的列
if (dfs()) return 1;//递归
for (int j = p[i].l; j != i; j = p[j].l) resume(p[j].col);//恢复
top--;
}
resume(mn);//恢复该列
return 0;
}
void solve() {
cin >> n >> m;
init();
for (int i = 1; i <= n; ++i) {
int hh = tot, tt = tot;
for (int j = 1; j <= m; ++j) {
int x;
cin >> x;
if (x) add(hh, tt, j, i);//插入十字链表并更新上下左右值
}
}
if (dfs()) {
for (int i = 1; i <= top; ++i) cout << ans[i] << " ";
}
else cout << "No Solution!";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
数独问题
AcWing169数独2
请你将一个 16×16 的数独填写完整,使得每行、每列、每个 4×4 十六宫格内字母 A∼P 均恰好出现一次。
保证每个输入只有唯一解决方案。
思路:将原树剖分成实链和虚边。实链是一条从上到下的路径,实链间通过虚边连接。将每条实链按节点深度构造成一棵splay树,链顶对应splay树最左节点,splay 根节点的父亲为该实链顶端节点的父亲仔细剖析数独,数独其实只有四个约束条件——
- 每行恰有 16 个不同的数;
- 每列恰有 16 个不同的数;
- 每个宫每恰有 16 个不同的数;
- 每个位置恰好有一个数。
所以,如果我们把数独每个位置裂成四个点,每个点对应一个条件,数独要求就变成了每个位置填一个适当的数,使全部条件都有且只有一个点满足。
用三元组 (i,j,k) 表示在 (i,j) 上的数是 k,对这个问题建模,每个可能的三元组构成 01 矩阵的一行,每个约束条件构成 01 矩阵的一列。
对于列,具体地说,有 16×16=256 列记录数独每个数是否出现,256 列记录数独 16 行每行 16 个数是否出现,256 列记录数独 16 列每列 16 个数是否出现,256 列记录数独 16 宫 16 个数是否出现,
一个完成的数独应当由 16×16=256 个三元组表出,并且每个三元组满足四个约束条件。换句话说,目标是选出 256 行,每行 4 个 1,并且每列恰好 1 个 1,这就转化成了 DLX 问题。
对于数独已确定的位置占一个三元组
(
i
,
j
,
a
i
,
j
)
(i,j,a_{i,j})
(i,j,ai,j),对于不确定的位置有 16 种可能,用 16 个三元组表示。
行的数量(最多):16×16×16=4096,列的数量:16×16×4=1024。只需枚举数独所有可能的情况,使用 DLX 算法判断是否可行。
所有数独问题都可以这么转化,特别的,当多解的数独不同位置有权重时,也可以直接记录每个节点的权重,找到答案后不返回,选择是否更新答案即可
#include <bits/stdc++.h>
using namespace std;
const int N = 20000;
int m = 16 * 16 * 4, u[N], d[N], l[N], r[N], s[N], col[N], row[N], idx, ans[N], top;
struct Op{
int x, y;
char z;
}op[N];
char g[20][20];
void init(){
for (int i = 0; i <= m; i++){
l[i] = i - 1, r[i] = i + 1;
s[i] = 0;
d[i] = u[i] = i;
}
l[0] = m, r[m] = 0;
idx = m + 1;
}
void add(int& hh, int& tt, int x, int y){
row[idx] = x, col[idx] = y, s[y] ++;
u[idx] = y, d[idx] = d[y], u[d[y]] = idx, d[y] = idx;
r[hh] = l[tt] = idx, r[idx] = tt, l[idx] = hh;
tt = idx++;
}
void remove(int p){
r[l[p]] = r[p], l[r[p]] = l[p];
for (int i = d[p]; i != p; i = d[i]) {
for (int j = r[i]; j != i; j = r[j]) {
s[col[j]] --;
u[d[j]] = u[j], d[u[j]] = d[j];
}
}
}
void resume(int p){
for (int i = u[p]; i != p; i = u[i]) {
for (int j = l[i]; j != i; j = l[j]) {
u[d[j]] = j, d[u[j]] = j;
s[col[j]] ++;
}
}
r[l[p]] = p, l[r[p]] = p;
}
bool dfs(){
if (!r[0]) return true;
int p = r[0];
for (int i = r[0]; i; i = r[i]) {
if (s[i] < s[p]) p = i;
}
remove(p);
for (int i = d[p]; i != p; i = d[i]){
ans[++top] = row[i];
for (int j = r[i]; j != i; j = r[j]) remove(col[j]);
if (dfs()) return true;
for (int j = l[i]; j != i; j = l[j]) resume(col[j]);
top--;
}
resume(p);
return false;
}
int main(){
while (~scanf("%s", g[0])){
for (int i = 1; i < 16; i++) scanf("%s", g[i]);
init();
for (int i = 0, n = 1; i < 16; i++) {
for (int j = 0; j < 16; j++) {
int a = 0, b = 15;
if (g[i][j] != '-') a = b = g[i][j] - 'A';
for (int k = a; k <= b; k++, n++) {
int hh = idx, tt = idx;
op[n] = { i, j, k + 'A' };
add(hh, tt, n, i * 16 + j + 1);
add(hh, tt, n, 256 + i * 16 + k + 1);
add(hh, tt, n, 256 * 2 + j * 16 + k + 1);
add(hh, tt, n, 256 * 3 + (i / 4 * 4 + j / 4) * 16 + k + 1);
}
}
}
dfs();
for (int i = 1; i <= top; i++){
auto t = op[ans[i]];
g[t.x][t.y] = t.z;
}
for (int i = 0; i < 16; i++) puts(g[i]);
puts("");
}
return 0;
}
重复覆盖问题
AcWing2713重复覆盖问题
给定一个 N×M 的数字矩阵 A,矩阵中的元素
A
i
,
j
A_{i,j}
Ai,j∈{0,1}。
请你在矩阵中找到一个行的集合,使得这些行中,每一列都包含数字 1,并且集合中包含的行数尽可能少。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e4 + 10;
struct node {
int l, r, u, d, row, col;
}p[N];
int n, m, cnt[N], tot, ans[N], st[110];
void init() {
for (register int i = 0; i <= m; ++i) {
p[i].l = i - 1, p[i].r = i + 1;
p[i].col = p[i].u = p[i].d = i;
}
p[0].l = m, p[m].r = 0;
tot = m + 1;
}
void add(int& hh, int& tt, int x, int y) {
p[tot].col = x, p[tot].row = y, cnt[x]++;
p[tot].u = x, p[tot].d = p[x].d;
p[x].d = p[p[x].d].u = tot;
p[hh].r = p[tt].l = tot;
p[tot].l = hh, p[tot].r = tt;
tt = tot++;
}
int h() {//估价函数,预估最少还需要选择多少行才能完全覆盖所有列
int cnt = 0;
for (register int i = 1; i <= m; ++i) st[i] = 0;
for (register int i = p[0].r; i; i = p[i].r) {
if (st[p[i].col]) continue;
cnt++;
st[p[i].col] = 1;
for (register int j = p[i].d; j != i; j = p[j].d) {
for (register int k = p[j].r; k != j; k = p[k].r) st[p[k].col] = 1;
}
}
return cnt;
}
void remove(int x) {
for (register int i = p[x].d; i != x; i = p[i].d) {
p[p[i].l].r = p[i].r, p[p[i].r].l = p[i].l;
}
}
void resume(int x) {
for (register int i = p[x].u; i != x; i = p[i].u) {
p[p[i].l].r = i, p[p[i].r].l = i;
}
}
bool dfs(int k, int dep) {
if (k + h() > dep) return 0;//若当前选择的行数+最少还要选择的行数>最多能选择的行数,无解
if (!p[0].r) return 1;
int mn = p[0].r;
for (register int i = p[0].r; i; i = p[i].r) {
if (cnt[i] < cnt[mn]) mn = i;
}
for (register int i = p[mn].d; i != mn; i = p[i].d) {
ans[k] = p[i].row;
remove(i);
for (register int j = p[i].r; j != i; j = p[j].r) remove(j);;
if (dfs(k + 1, dep)) return 1;
for (register int j = p[i].l; j != i; j = p[j].l) resume(j);
resume(i);
}
return 0;
}
void solve() {
cin >> n >> m;
init();
for (register int i = 1; i <= n; ++i) {
int hh = tot, tt = tot;
for (register int j = 1; j <= m; ++j) {
int x;
cin >> x;
if (x) add(hh, tt, j, i);
}
}
int dep = 0;
while (!dfs(0, dep)) dep++;//迭代加深
cout << dep << '\n';
for (int i = 0; i < dep; ++i) cout << ans[i] << " ";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
52.CDQ分治
三维偏序
P3810 【模板】三维偏序(陌上花开)
有
n
n
n 个元素,第
i
i
i 个元素有
a
i
,
b
i
,
c
i
a_i,b_i,c_i
ai,bi,ci 三个属性,设
f
(
i
)
f(i)
f(i) 表示满足
a
j
≤
a
i
a_j \leq a_i
aj≤ai 且
b
j
≤
b
i
b_j \leq b_i
bj≤bi 且
c
j
≤
c
i
c_j \leq c_i
cj≤ci 且
j
≠
i
j \ne i
j=i 的
j
j
j 的数量。
对于
d
∈
[
0
,
n
)
d \in [0, n)
d∈[0,n) ,求
f
(
i
)
=
d
f(i) = d
f(i)=d 的数量。
思路:先按 a 维排序,再将左、右子区间按 b 维归并排序。虽然现在 a 的顺序被打乱了,但是前半边还是都小于后半边的,所以要是只计算前半边对后半边的偏序关系,是不会受到 a 的影响的。考虑到 b 已经排序了,直接双指针,树状数组统计 c 维贡献即可。
O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 2e5 + 10;
struct node {
int a, b, c, cnt, ans;
bool operator<(node t) {
if (a != t.a) return a < t.a;
else if (b != t.b) return b < t.b;
else return c < t.c;
}
bool operator==(node t) {
return a == t.a && b == t.b && c == t.c;
}
}p[N], tmp[N];
int n, k, ans[N], c[N];
int lowbit(int x) {
return x & -x;
}
void add(int x, int y) {
for (; x <= k; x += lowbit(x)) c[x] += y;
}
int query(int x) {
int res = 0;
for (; x; x -= lowbit(x)) res += c[x];
return res;
}
void mergesort(int l, int r) {//对b归并排序
if (l >= r) return;
int mid = l + r >> 1;
mergesort(l, mid), mergesort(mid + 1, r);
int L = l, R = mid + 1;
int len = 0;
while (L <= mid && R <= r) {
if (p[L].b <= p[R].b) add(p[L].c, p[L].cnt), tmp[++len] = p[L], L++;//c加入树状数组
else p[R].ans += query(p[R].c), tmp[++len] = p[R], R++;
}
while (L <= mid) add(p[L].c, p[L].cnt), tmp[++len] = p[L], L++;
while (R <= r) p[R].ans += query(p[R].c), tmp[++len] = p[R], R++;//处理剩余
for (int i = l; i <= mid; ++i) add(p[i].c, -p[i].cnt);//清空树状数组
for (int i = l, j = 1; j <= len; ++i, ++j) p[i] = tmp[j];
}
void solve() {
cin >> n >> k;
for (int i = 1; i <= n; ++i) {
cin >> p[i].a >> p[i].b >> p[i].c;
p[i].cnt = 1;
}
sort(p + 1, p + n + 1);//按a排序
int len = 0;
for (int i = 1; i <= n; ++i) {//去重+统计重复次数
if (p[i] == p[len]) p[len].cnt++;
else p[++len] = p[i];
}
mergesort(1, len);
for (int i = 1; i <= len; ++i) ans[p[i].ans + p[i].cnt - 1] += p[i].cnt;
for (int i = 0; i < n; ++i) cout << ans[i] << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
53.01Trie
P4551 最长异或路径
给定一棵
n
n
n 个点的带权树,结点下标从
1
1
1 开始到
n
n
n。寻找树中找两个结点,求最长的异或路径。
异或路径指的是指两个结点之间唯一路径上的所有边权的异或。
思路:对于每一位进行贪心,如果这一位有一个与它不同的,即异或后是1,那我们就顺着这条路往下走;否则就顺着原路往下走。
O ( n ∗ l e n ) O(n*len) O(n∗len)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 2e6 + 10;
struct edge {
int u, v, w;
};
vector<vector<edge> >e(N);
int tr[N][2], tot, sum[N];//sum点到根异或和
void insert(int k) {
int p = 0;
for (int i = 30; i >= 0; --i) {
int x = (k >> i) & 1;
if (!tr[p][x]) tr[p][x] = ++tot;
p = tr[p][x];
}
}
int query(int k) {
int p = 0, res = 0;
for (int i = 30; i >= 0; --i) {
int x = (k >> i) & 1;
if (tr[p][!x]) {
res += (1 << i);
p = tr[p][!x];
}
else p = tr[p][x];
}
return res;
}
void dfs(int x, int fa) {
for (auto i : e[x]) {
if (i.v == fa) continue;
sum[i.v] = sum[x] ^ i.w;
insert(sum[i.v]);
dfs(i.v, x);
}
}
void solve() {
int n;
cin >> n;
for (int i = 1; i < n; ++i) {
int u, v, w;
cin >> u >> v >> w;
e[u].push_back({ u,v,w });
e[v].push_back({ v,u,w });
}
dfs(1, 0);
insert(0);
int ans = 0;
for (int i = 1; i <= n; ++i) {
ans = max(ans, query(sum[i]));
}
cout << ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin >> _t;
while (_t--) {
solve();
}
return 0;
}
54.可持久化01Trie
P4735 最大异或和
给定一个非负整数序列
{
a
}
\{a\}
{a},初始长度为
N
N
N。
有
M
M
M 个操作,有以下两种操作类型:
A x
:添加操作,表示在序列末尾添加一个数 x x x,序列的长度 N N N 加 1 1 1。Q l r x
:询问操作,你需要找到一个位置 p p p,满足 l ≤ p ≤ r l \le p \le r l≤p≤r,使得: a [ p ] ⊕ a [ p + 1 ] ⊕ . . . ⊕ a [ N ] ⊕ x a[p] \oplus a[p+1] \oplus ... \oplus a[N] \oplus x a[p]⊕a[p+1]⊕...⊕a[N]⊕x 最大,输出最大值。
思路:异或满足可减性,所以可以维护前缀和,然后a[p] xor a[p+1] xor … xor a[n] xor x=s[p−1] xor s[n] xor x,然后就只要维护 s[]。
此时查询转变为:已知 val=s[n] xor x,求一个 p∈[l−1,r−1],使得 s[p] xor val 最大。
可以构建一颗可持久化 0/1 Trie,第 i 个版本为插入了 s[i] 后的 Trie 树。
每次查询,从根节点开始,贪心地选与这一位相反的值。
此外,还有一个 l−1 ≤ p ≤ r−1 的限制。先考虑 p ≤ r−1,查询第 r−1 个版本的 Trie 即可,因为此时不可能访问到r−1之后的s。再考虑 l−1 ≤ p。对每个节点维护一个 latest,表示子树中所有 s 值的下标的最大值。这样,在查询时只访问 latest ≥ l−1 的节点就行了。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e8 + 10;
int tr[N][2], tot, pre, root[N], cnt[N];//cnt节点的数字个数
void insert(int rt, int k) {//在rt基础上插入k
int p = tot, q = rt;
for (int i = 24; i >= 0; --i) {
tr[p][0] = tr[q][0], tr[p][1] = tr[q][1];
int x = (k >> i) & 1;
tr[p][x] = ++tot;
p = tr[p][x], q = tr[q][x];
cnt[p] = cnt[q] + 1;
}
}
int query(int p, int q, int k) {
int res = 0;
for (int i = 24; i >= 0; --i) {
int x = (k >> i) & 1;
if (cnt[tr[q][!x]] > cnt[tr[p][!x]]) {//保证l−1 ≤ p
res += (1 << i);
p = tr[p][!x], q = tr[q][!x];
}
else p = tr[p][x], q = tr[q][x];
}
return res;
}
void solve() {
int n, m;
cin >> n >> m;
root[0] = ++tot;
insert(0, 0);
for (int i = 1; i <= n; ++i) {
int x;
cin >> x;
pre ^= x;
root[i] = ++tot;
insert(root[i - 1], pre);
}
for (int i = 1; i <= m; ++i) {
char op;
cin >> op;
if (op == 'A') {
int x;
cin >> x;
++n;
pre ^= x;
root[n] = ++tot;
insert(root[n - 1], pre);
}
else {
int l, r, x;
cin >> l >> r >> x;
if (l == 1) cout << query(0, root[r - 1], pre ^ x) << '\n';
else cout << query(root[l - 2], root[r - 1], pre ^ x) << '\n';
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin >> _t;
while (_t--) {
solve();
}
return 0;
}
55.树同构
P5043 【模板】树同构([BJOI2015]树的同构)
树是一种很常见的数据结构。
我们把
N
N
N 个点,
N
−
1
N-1
N−1 条边的连通无向图称为树。
若将某个点作为根,从根开始遍历,则其它的点都有一个前驱,这个树就成为有根树。
对于两个树
T
1
T_1
T1 和
T
2
T_2
T2,如果能够把树
T
1
T_1
T1 的所有点重新标号,使得树
T
1
T_1
T1 和树
T
2
T_2
T2 完全相同,那么这两个树是同构的。也就是说,它们具有相同的形态。
现在,给你
M
M
M 个无根树,请你把它们按同构关系分成若干个等价类。
树哈希
思路:求出以每个点为根时的哈希值,排序后比较。
还可以通过找重心的方式来优化复杂度。一棵树的重心最多只有两个,只需把以它(们)为根时的哈希值求出来即可。接下来,既可以分别比较这些哈希值,也可以在有一个重心时取它的哈希值作为整棵树的哈希值,有两个时则取其中较小(大)的。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 55;
vector<vector<int> >e(N);
ll ans[N][N];
ll mask = 2333;
ll shift(ll x) {
x ^= mask;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
x ^= mask;
return x;
}
ll Hash(int x, int fa) {
ll son[N], cnt = 0, ans = 0;
for (int i : e[x]) {
if (i == fa) continue;
son[++cnt] = Hash(i, x);
}
sort(son + 1, son + cnt + 1);
for (int i = 1; i <= cnt; ++i) ans = ans * 2333 + son[i];
return ans * 2333 + 1;
//for (int i = 1; i <= cnt; ++i) ans += shift(son[i]);
//return ans + 1;
}
void solve() {
int m;
cin >> m;
for (int i = 1; i <= m; ++i) {
e.clear();
e.resize(N);
int n;
cin >> n;
for (int j = 1; j <= n; ++j) {
int u;
cin >> u;
if (u) e[u].push_back(j), e[j].push_back(u);
}
for (int j = 1; j <= n; ++j) ans[i][j] = Hash(j, 0);//计算以每个点为根的哈希值
sort(ans[i] + 1, ans[i] + n + 1);
for (int j = 1; j <= i; ++j) {
int k;
for (k = 1; k <= n; ++k) {
if (ans[j][k] != ans[i][k]) break;
}
if (k > n) {
cout << j << '\n';
break;
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
AHU算法
思路:一段合法的欧拉序和一棵有根树唯一对应,而且一棵树的欧拉序是由它的子树的欧拉序拼接而成的。如果我们通过改变子树欧拉序拼接的顺序,从而获得了一段新的欧拉序,那么新欧拉序对应的树和原欧拉序对应的树同构。
对于无根树 T1和 T2,先分别找出它们的 所有重心。
如果这两棵无根树重心数量不同,那么这两棵树不同构。
如果这两颗无根树重心数量都为 1,那么如果有根树 T1和有根树 T2同构,那么无根树 T1和 T2同构,反之则不同构。
如果这两颗无根树重心数量都为 2,那么如果有根树 T1和有根树 T2同构 或者有根树 T1和 T2′ 同构,那么无根树 T1和 T2同构,反之则不同构。
O ( m n 2 ) O(mn^2) O(mn2)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 55;
vector<vector<int> >e(N);
int m, n, mn,siz[N], zx[N], cnt;
string f[N], son[N], tmp, ans[N];
void dfs1(int x, int fa) {
siz[x] = 1;
int tmp = 0;
for (int i : e[x]) {
if (i == fa) continue;
dfs1(i, x);
siz[x] += siz[i];
tmp = max(tmp, siz[i]);
}
tmp = max(tmp, n - siz[x]);
if (tmp < mn) {
mn = tmp;
cnt = 0;
zx[++cnt] = x;
}
else if (tmp == mn) zx[++cnt] = x;
}
void dfs2(int x, int fa) {
f[x] = "0";
for (int i : e[x]) {
if (i == fa) continue;
dfs2(i, x);
}
int tot = 0;
for (int i : e[x]) {
if (i == fa) continue;
son[++tot] = f[i];
}
sort(son + 1, son + tot + 1);
for (int i = 1; i <= tot; ++i) f[x] += son[i];
f[x] += "1";
return;
}
void solve() {
cin >> m;
for (int i = 1; i <= m; ++i) {
e.clear();
e.resize(N);
tmp = "1", mn = 0x3f3f3f3f, cnt = 0;
cin >> n;
for (int j = 1; j <= n; ++j) {
int u;
cin >> u;
if (u) e[u].push_back(j), e[j].push_back(u);
}
dfs1(1, 0);//求重心
for (int j = 1; j <= cnt; ++j) {
dfs2(zx[j], 0);
tmp = min(tmp, f[zx[j]]);//求最小欧拉序
}
ans[i] = tmp;
for (int j = 1; j <= i; ++j) {
if (ans[j] == ans[i]) {
cout << j << '\n';
break;
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//cin>>_t;
while (_t--) {
solve();
}
return 0;
}
56.树分块
树上撒点。设一个阈值 S,并考虑在树上选择不超过 n S \frac{n}{S} Sn 个点作为关键点,满足每个关键点到离它最近的祖先关键点的距离不超过S
P6177 Count on a tree II/【模板】树分块
给定一个
n
n
n 个节点的树,每个节点上有一个整数,
i
i
i 号点的整数为
v
a
l
i
val_i
vali。
有
m
m
m 次询问,每次给出
u
′
,
v
u',v
u′,v,您需要将其解密得到
u
,
v
u,v
u,v,并查询
u
u
u 到
v
v
v 的路径上有多少个不同的整数。
解密方式:
u
=
u
′
xor
l
a
s
t
a
n
s
u=u' \operatorname{xor} lastans
u=u′xorlastans。
l
a
s
t
a
n
s
lastans
lastans 为上一次询问的答案,若无询问则为
0
0
0。
每次选择一个深度最大的非关键点,若它的1∼S级祖先都不是关键点,则把它的S级祖先标记为关键点。考虑一条到根路径上的所有关键点,用bitset维护它们两两间出现的颜色。求出 u,v 的祖先中离 u,v 最近的关键点 u0,v0,以及离 lca 最近且在 t 子树中的关键点 u1,v1。那么一条路径被我们拆成了以下几个部分:u∼u0,v∼v0,u1∼lca,v1∼lca,u0∼u1,v0∼v1。对于前面 4 种,直接暴力跳。而对于后面两种,我们已经预处理它们的 bitset,所以直接取并集。
O
(
(
n
+
m
)
n
+
n
2
+
n
m
w
)
O((n+m)\sqrt n+\frac{n^2+nm}{w})
O((n+m)n+wn2+nm)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<ll,ll>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 5e4 + 10;
vector<vector<int> >e(N);
bitset<N> bit[210][210], ans;
int a[N], b[N], dep[N], mxdep[N], f[N][20], id[N], st[N], cnt, top, fa[N];
void dfs(int x, int father) {
mxdep[x] = dep[x] = dep[father] + 1;
f[x][0] = father;
for (int i = 1; i < 20; ++i) f[x][i] = f[f[x][i - 1]][i - 1];
for (int i : e[x]) {
if (i == father) continue;
dfs(i, x);
mxdep[x] = max(mxdep[x], mxdep[i]);
}
//每次选择一个深度最大的非关键点,若它的1∼S级祖先都不是关键点,则把它的S级祖先标记为关键点
if (mxdep[x] - dep[x] >= 200) {
id[x] = ++cnt;
mxdep[x] = dep[x];
}
}
void dfs2(int x, int father) {//考虑一条到根路径上的所有关键点,用bitset维护它们两两间出现的颜色
for (int i : e[x]) {
if (i == father) continue;
if (id[i]) {
int u = id[st[top]], v = id[i];
for (int j = i; j != st[top]; j = f[j][0]) bit[u][v].set(a[j]);
for (int j = 1; j < top; ++j) bit[id[st[j]]][v] = bit[id[st[j]]][u] | bit[u][v];
fa[i] = st[top];
st[++top] = i;
}
dfs2(i, x);
if (id[i]) top--;
}
}
int lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
for (int i = 19; i >= 0; --i) {
if (dep[f[x][i]] >= dep[y]) x = f[x][i];
}
if (x == y) return x;
for (int i = 19; i >= 0; --i) {
if (f[x][i] != f[y][i]) {
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
inline void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i) cin >> a[i], b[i] = a[i];
sort(b + 1, b + n + 1);
int len = unique(b + 1, b + n + 1) - b - 1;
for (int i = 1; i <= n; ++i) a[i] = lower_bound(b + 1, b + len + 1, a[i]) - b;
for (int i = 1; i < n; ++i) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs(1, 0);
if (!id[1]) id[1] = ++cnt;
st[top = 1] = 1;
dfs2(1, 0);
int last = 0;
for (int i = 1; i <= m; ++i) {
int u, v;
cin >> u >> v;
u ^= last;
ans.reset();
int d = lca(u, v);
while (u != d && !id[u]) ans.set(a[u]), u = f[u][0];//u∼u0,v∼v0
while (v != d && !id[v]) ans.set(a[v]), v = f[v][0];
if (u != d) {
int p = u;
while (dep[fa[p]] >= dep[d]) p = fa[p];//u0∼u1,v0∼v1
ans |= bit[id[p]][id[u]];
while (p != d) ans.set(a[p]), p = f[p][0];//u1∼lca,v1∼lca
}
if (v != d) {
int p = v;
while (dep[fa[p]] >= dep[d]) p = fa[p];
ans |= bit[id[p]][id[v]];
while (p != d) ans.set(a[p]), p = f[p][0];
}
ans.set(a[d]);
last = ans.count();
cout << last << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//_t = read();
while (_t--) {
solve();
}
return 0;
}
57.珂朵莉树
珂朵莉树是一种以近乎暴力的形式存储区间信息的一个数据结构。方式是通过set存放若干个用结构体表示的区间,每个区间的元素都是相同的。只要是涉及到区间赋值操作的题,就可以用珂朵莉树处理几乎任何关于区间信息的询问。珂朵莉树要求题目必须存在区间赋值操作,且数据有高度的随机性。
CF896C Willem, Chtholly and Seniorious
请你写一种奇怪的数据结构,支持:
- 1 1 1 l l l r r r x x x :将 [ l , r ] [l,r] [l,r] 区间所有数加上 x x x
- 2 2 2 l l l r r r x x x :将 [ l , r ] [l,r] [l,r] 区间所有数改成 x x x
- 3 3 3 l l l r r r x x x :输出将 [ l , r ] [l,r] [l,r] 区间从小到大排序后的第 x x x 个数是的多少(即区间第 x x x 小,数字大小相同算多次,保证 1 ≤ 1\leq 1≤ x x x ≤ \leq ≤ r − l + 1 r-l+1 r−l+1 )
- 4 4 4 l l l r r r x x x y y y :输出 [ l , r ] [l,r] [l,r] 区间每个数字的 x x x 次方的和模 y y y 的值(即( ∑ i = l r a i x \sum^r_{i=l}a_i^x ∑i=lraix ) m o d y \mod y mody )
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<ll,ll>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct node {
ll l, r;
mutable ll v;//mutable关键字定义一个强制可变量
bool operator<(const node a)const {
return l < a.l;
}
};
set<node> tr;
ll n, m, seed, vmax, a[N];
ll rnd() {
ll ret = seed;
seed = (seed * 7 + 13) % mod;
return ret;
}
auto split(ll pos) {//从set中的区间中找到pos所在的区间,拆成两个区间
auto it = tr.lower_bound({ pos,0,0 });
if (it != tr.end() && it->l == pos) return it;
it--;
if (it->r < pos) return tr.end();
ll l = it->l, r = it->r, v = it->v;
tr.erase(it);//删除区间
tr.insert({ l,pos - 1,v });//重新插入两个区间
return tr.insert({ pos,r,v }).first;//返回以pos开头的区间的迭代器
}
void add(ll l, ll r, ll x) {
auto itr = split(r + 1), itl = split(l);
for (auto i = itl; i != itr; ++i) i->v += x;
}
void merge(ll l, ll r, ll x) {//区间赋值,合并
auto itr = split(r + 1), itl = split(l);//先r+1,再l
tr.erase(itl, itr);//删除这些区间
tr.insert({ l,r,x });//重新插入区间
}
ll kth(ll l, ll r, ll k) {
vector<P> v;
auto itr = split(r + 1), itl = split(l);
for (auto i = itl; i != itr; ++i) v.push_back({ i->v,i->r - i->l + 1 });
sort(v.begin(), v.end());
for (auto i : v) {
k -= i.second;
if (k <= 0) return i.first;
}
}
ll ksm(ll a, ll b, ll p) {
ll res = 1;
a %= p;
while (b) {
if (b & 1) res = res * a % p;
b >>= 1;
a = a * a % p;
}
return res;
}
ll query(ll l, ll r, ll x, ll y) {
ll res = 0;
auto itr = split(r + 1), itl = split(l);
for (auto i = itl; i != itr; ++i) (res += ksm(i->v, x, y) * (i->r - i->l + 1) % y) %= y;
return res;
}
inline void solve() {
cin >> n >> m >> seed >> vmax;
for (int i = 1; i <= n; ++i) {
a[i] = rnd() % vmax + 1;
tr.insert({ i, i, a[i] });
}
for (int i = 1; i <= m; ++i) {
ll op = rnd() % 4 + 1, l = rnd() % n + 1, r = rnd() % n + 1;
if (l > r) swap(l, r);
if (op == 1) {
ll x = rnd() % vmax + 1;
add(l, r, x);
}
else if (op == 2) {
ll x = rnd() % vmax + 1;
merge(l, r, x);
}
else if (op == 3) {
ll x = rnd() % (r - l + 1) + 1;
cout << kth(l, r, x) << '\n';
}
else {
ll x = rnd() % vmax + 1, y = rnd() % vmax + 1;
cout << query(l, r, x, y) << '\n';
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _t = 1;
//_t = read();
while (_t--) {
solve();
}
return 0;
}