07.13 数据结构
P4735
转化到区间求 \(\text{xor} \ x\) 后的最大值,用 Trie。那么需要知道区间是否有在 Trie 树某个子树内的节点,用可持久 Trie,或者离线扫右端点并记录左端点时间戳即可。
第二个做法可以不离线,同样使用可持久 Trie,但是求区间时不使用减法,而是只使用插入前 \(r\) 个数的 Trie,通过在每个节点记录该节点子树内所有点的最小值,来判断值域区间内是否有点。
写了第一个。两个做法还有一个没调出来。
凑区间的另一个方法是树状数组那种拆分,不要求信息有可减性,那么每个点会被插进 \(\log\) 个 Trie 中。好像可以动态修改。好像达成了修改的查询的平衡。复杂度多一只 \(\log\)。
P5283
\(i < j\):直接去掉,让 \(k \gets 2k\)。另一个做法是拆分 \(i\) 的取值区间,初始为 \([1, j-1]\),每取走一个值分裂一次。或者直接给原做法套可持久化 Trie。本来要求异或 \(k\) 大值,现在要求区间极值。本质都在寻找下一大值。
维护所有可能成为下一个答案的数值,一边扫描一边加新的值进来。
具体而言,维护一个堆,当取出堆顶时,设它由 \(a_u\) 产生,取与 \(a_u\) 异或值最大的小于刚取出的值的数值入堆,它为下一个可能成为答案的数值。(考虑到相等事实上是排名为下一个)复杂度 \(O(k \log n)\)。
需要快速找到排名为下一位的通过 \(a_u\) 产生的数值。
拓展:不依据 \(k\) 的做法。
先快速求出第 \(k\) 大的值。逐位确定之。若现在确定了 \(i\) 位,对于每个 \(a_x\) 找一个 \(b_x\),使得 \(b_x\) 的深度为 \(i\),且 \(a_x \ \text{xor} \ b_x\) 的前 \(i\) 位为答案,这样可以确定下一位的方向。找到答案后,需要求所有比答案大的异或值之和。对于每个 \(a_i\),通过枚举它与最大值第一位不一样处,得到答案最多位于 \(\log\) 棵子树内。而 Trie 的子树是原数组排序后连续一段,拆位做前缀和即可。复杂度 \(O(n \log n \log V)\)。
都是对 \(n\) 个 \(a_i\) 逐位限制操作。
references:
https://www.luogu.com.cn/article/ipa5pxaj
https://www.luogu.com.cn/article/mdn29sxp
P2633
用 \(1 \to u \ + \ 1 \to v \ - \ 2 \cdot 1 \to \text{lca}(u, v)\) 造路径即可。在每个点处维护一棵值域线段树,为了空间需要可持久化。
换非递归 Trie 试试。
如果想带修估计要树剖+树状数组,三只 \(\log\)。看着好蠢,想不到更好的做法。
uoj218
操作一是区间覆盖,操作三是区间求和,操作二是单点回退版本。如果知道要回退到的值,就可以用线段树维护。至于值,用可持久化线段树维护。修改时添加时间信息,即可得知回退到哪个版本。
在下传时新建节点,供后面版本使用。也可以标记永久化,只要标记有结合律就行。
另一个方法是,对于区间覆盖的主席树,把左右儿子指向自身
https://blog.csdn.net/lvzelong2014/article/details/88202901
hdu4348
区间加,历史版本区间和,回退历史版本。
标记永久化即可。因为复制一个节点时,将其标记一同复制,容易仿造普通线段树证明它的正确性。
P4768
先求单源最短路,找一个位置 \(v\),使得 \(u \to v\) 可以通过不小于 \(d\) 的边连通,且 \(dis_v\) 最小。
如果离线可以按 \(d\) 排序并维护并查集,如果在线可以维护可持久化并查集。
另一个做法是利用 Krustal 重构树。
求出最大生成树的 Krustal 重构树,从 \(u\) 往上跳到不能跳为止,则整棵子树为只经过不小于 \(d\) 的边能到达的点。在子树内求个 \(\min\) 即可,这是可以在建树的过程中直接处理的。
可持久化并查集能做的东西好像 Krustal 重构树基本上都能做,因为需要的连通块信息在 Krustal 重构树上是一棵子树。
#include <bits/stdc++.h>
std::vector<int> fa;
int find(int u) {
return fa[u] == u ? u : fa[u] = find(fa[u]);
}
void solve() {
int n, m; scanf("%d %d", &n, &m);
int k = std::log2(std::max(n - 1, 1)) + 1;
std::vector<std::vector<std::pair<int, int>>> e(n);
std::vector<std::tuple<int, int, int>> r(m);
for (auto &[a, u, v] : r) {
int l;
scanf("%d %d %d %d", &u, &v, &l, &a), --u, --v;
e[u].push_back({v, l}), e[v].push_back({u, l});
}
std::sort(r.begin(), r.end(), [&](const auto &x, const auto &y) -> bool {
return std::get<0>(x) > std::get<0>(y);
});
std::vector<int> vis(n), dis(n, int(1e9)), md(2 * n, __INT_MAX__);
dis[0] = 0;
std::priority_queue<std::pair<int, int>> q;
q.push({0, 0});
while (q.size()) {
int u = q.top().second; q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (const auto &[v, w] : e[u]) {
if (dis[u] + w < dis[v]) {
dis[v] = dis[u] + w;
q.push({-dis[v], v});
}
}
}
for (int i = 0; i < n; i++)
md[i] = dis[i];
fa.resize(2 * n), std::iota(fa.begin(), fa.end(), 0);
int cnt = n;
std::vector<std::vector<int>> f(2 * n, std::vector<int>(k, -1)),
mn(2 * n, std::vector<int>(k, __INT_MAX__)), ch(2 * n);
for (auto [a, u, v] : r) if ((u = find(u)) != (v = find(v))) {
ch[cnt].push_back(u), ch[cnt].push_back(v);
fa[u] = fa[v] = cnt;
mn[u][0] = mn[v][0] = a;
md[cnt] = std::min(md[u], md[v]);
++cnt;
}
std::function<void(int)> dfs = [&](int u) -> void {
for (int i = 1; i < k; i++) {
if (f[u][i - 1] == -1) break;
f[u][i] = f[f[u][i - 1]][i - 1];
mn[u][i] = std::min(mn[u][i - 1], mn[f[u][i - 1]][i - 1]);
}
for (auto v : ch[u])
f[v][0] = u, dfs(v);
};
dfs(cnt - 1);
int Q, K, S; scanf("%d %d %d", &Q, &K, &S);
int lst = 0;
while (Q--) {
int u, p; scanf("%d %d", &u, &p);
u = (u + K * lst - 1) % n;
p = (p + K * lst) % (S + 1);
// std::cerr << u << " " << p << std::endl;
for (int i = k - 1; i >= 0; i--) if (f[u][i] != -1 && mn[u][i] > p)
u = f[u][i];
printf("%d\n", md[u]);
lst = md[u];
}
}
int main() {
int T; scanf("%d", &T); while (T--) {
solve();
}
}
P3345
仿造不带权重心,去掉带权重心,树分成很多块,每一块大小不超过原树的一半。
从 \(1\) 出发,跳重儿子,直到找到最深的满足 \(2 siz_x \geq siz_1\) 的点,即为重心。
因为每个点的度数不多,轻重链切分时可以枚举所有儿子判断行走方向。复杂度 \(O(d n \log^2 n)\),\(d\) 为度数。
求出带权重心后拆贡献求带权距离之和。
补一下点分树做法。
以 \(u\) 为根,若存在 \(2siz_v \geq siz_u\),则重心在 \(v\) 子树内;否则 \(u\) 即为重心。
为了能一步步跳儿子,使用点分树保证只用往下走 \(\log\) 次。
因为儿子并不多,可以暴力统计换根时答案。至于树上距离和可以暴力跳点分树解决。
复杂度 \(O(d n \log^2 n)\),同上面那个。
在点分树上解决以固定点为一端点的路径问题时,往往跳点分树的父亲并进行容斥。因此,每个点处需要存储的信息为:其子树内所有点到它本身的信息、其子树内所有点到它父亲的信息。
#include <bits/stdc++.h>
using LL = long long;
std::vector<std::vector<std::pair<int, int>>> e;
std::vector<int> mx, siz, vis, fa, cnd, pre;
int main() {
int n, m; scanf("%d %d", &n, &m);
e.resize(n);
for (int i = 1, a, b, c; i < n; i++) {
scanf("%d %d %d", &a, &b, &c), --a, --b;
e[a].push_back({b, c}), e[b].push_back({a, c});
}
std::vector<int> dfn(n), dep(n);
std::vector<LL> ds(n);
int k = std::log2(n - 1) + 1;
std::vector<std::vector<int>> mi(k, std::vector<int>(n));
int dfc = 0;
auto mn = [&](int x, int y) {
if (x == -1 || y == -1) return -1;
return dep[x] < dep[y] ? x : y;
};
{
std::function<void(int, int)> dfs = [&](int u, int f) {
assert(u < n && u >= 0);
mi[0][dfn[u] = dfc++] = f;
for (const auto &[v, w] : e[u]) if (v != f)
dep[v] = dep[u] + 1, ds[v] = ds[u] + w, dfs(v, u);
};
dfs(0, -1);
for (int i = 1; i < k; i++)
for (int j = 0; j + (1 << i) - 1 < n; j++)
mi[i][j] = mn(mi[i - 1][j], mi[i - 1][j + (1 << i - 1)]);
}
auto lca = [&](int u, int v) {
if (u == v) return u;
if ((u = dfn[u]) > (v = dfn[v])) std::swap(u, v);
int x = std::log2(v - u++);
return mn(mi[x][u], mi[x][v - (1 << x) + 1]);
};
auto dis = [&](int u, int v) {
return ds[u] + ds[v] - 2 * ds[lca(u, v)];
};
pre = cnd = fa = mx = siz = vis = std::vector<int>(n);
std::vector<std::vector<int>> ch(n);
auto getrt = [&](int u) -> int {
int rt = -1, tot = siz[u];
std::function<void(int, int)> dfs = [&](int u, int fa) {
mx[u] = 0, siz[u] = 1;
for (const auto &[v, w] : e[u]) if (!vis[v] && v != fa)
dfs(v, u), siz[u] += siz[v], mx[u] = std::max(mx[u], siz[v]);
mx[u] = std::max(mx[u], tot - siz[u]);
if (rt == -1 || mx[u] < mx[rt]) rt = u;
};
return dfs(u, -1), cnd[u] = rt;
};
std::function<void(int)> dfs = [&](int u) {
vis[u] = 1;
static std::function<void(int, int)> calc = [&](int x, int fa) {
siz[x] = 1;
for (const auto &[y, z] : e[x]) if (!vis[y] && y != fa)
calc(y, x), siz[x] += siz[y];
};
calc(u, -1);
for (const auto &[v, w] : e[u]) if (!vis[v]) {
int x = getrt(v);
fa[x] = u, ch[u].push_back(x);
pre[x] = v;
dfs(x);
}
};
siz[0] = n;
fa.assign(n, -1);
int rt = -1;
dfs(rt = getrt(0));
std::vector<LL> sum(n), s1(n), s2(n);
while (m--) {
int x, v; scanf("%d %d", &x, &v), --x;
for (int y = x; y != -1; y = fa[y]) {
// std::cerr << y << std::endl;
sum[y] += v;
s1[y] += v * dis(x, y);
if (fa[y] != -1) s2[y] += v * dis(x, fa[y]);
}
std::function<LL(int)> qry = [&](int x) {
static auto calc = [&](int u) -> LL {
LL ans = s1[u];
int v = u;
for (v = u; fa[v] != -1; v = fa[v]) {
ans += s1[fa[v]] - s2[v] + dis(fa[v], u) * (sum[fa[v]] - sum[v]);
}
return ans;
};
LL cur = calc(x);
for (int v : ch[x]) {
if (calc(pre[v]) < cur) return qry(v);
}
return cur;
};
printf("%lld\n", qry(rt));
}
}
SPOJ QTREE5
点分树。
每个节点开一个堆维护所有合法点到其距离,因为重复走不优,不用考虑去重的事。
非常暴力。
scanf("%d", &m);
int cnt = 0;
std::vector<int> col(n);
std::vector<std::set<std::pair<int, int>>> s(n);
while (m--) {
int op, x; scanf("%d %d", &op, &x), --x;
if (op == 0) {
if (col[x] == 0) {
++cnt, col[x] = 1;
for (int y = x; y != -1; y = fa[y]) {
s[y].insert({dis(x, y), x});
}
} else {
--cnt, col[x] = 0;
for (int y = x; y != -1; y = fa[y])
s[y].erase({dis(x, y), x});
}
} else {
if (!cnt) printf("-1\n");
else {
int ans = n + 1;
for (int y = x; y != -1; y = fa[y]) {
if (s[y].size()) ans = std::min(ans, dis(x, y) + s[y].begin()->first);
}
printf("%d\n", ans);
}
}
}
P5311
成都七中。
首先处理连通块的事。任意一个原树连通块都会被完全包含在点分树中某个点的子树内。于是在保证合法的情况下往上跳,记录它到祖先的路径的 \(\min\) 与 \(\max\),这样成为了子树问题。
现在枚举每个子树。因为 \(O(\sum siz) = O(n \log n)\),复杂度是合理的。扫描线,扫右端点,对每种颜色 \(c\) 维护最大的 \(l\),使得存在 \(v\),\(col_v=c\),且 \(u \to v\) 路径上的 \(\min\) 为 \(l\)。
套路吗,感觉还好。在意识到连通块的任意一个点都可以代替原连通块的情况下转子树问题大概是最难的一步。
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int M = 2e5 + 5;
vector<int> e[M];
#define lowbit(x) (x & -x)
int cl[M], n, m;
int v[M];
void upd(int u, int x) { for(; u <= n; u += lowbit(u)) v[u] += x; }
int query(int u) { int res = 0; for (; u; u -= lowbit(u)) res += v[u]; return res; }
struct ev {
int x, y, z, t;
} ;
int siz[M]; bool vis[M];
vector<ev> anc[M], q[M];
int rt;
// 重新计算子树大小并连边
void dfs(int u, int fa, int mn = 1e9, int mx = -1e9) {
mn = min(mn, u), mx = max(mx, u);
anc[u].push_back({mn, mx, rt}), q[rt].push_back({mn, mx, cl[u], 0});
siz[u] = 1;
for (auto v : e[u]) {
if(v == fa || vis[v]) continue;
dfs(v, u, mn, mx); siz[u] += siz[v];
}
}
int ss, mx, pl;
// 计算重心
void get(int u, int fa) {
int m = 0; siz[u] = 1;
for (auto v : e[u]) {
if(v == fa || vis[v]) continue;
get(v, u); siz[u] += siz[v]; m = max(m, siz[v]);
}
m = max(m, ss - siz[u]); if(m < mx) mx = m, pl = u;
}
// 返回重心
inline int get(int u) {
ss = siz[u]; mx = 1e9; get(u, 0); return pl;
}
// 直接传入重心
void build(int u) {
vis[u] = 1; rt = u; dfs(u, 0);
for (auto v : e[u]) {
if(vis[v]) continue;
build(get(v));
}
}
int ans[M], mn[M];
void solve() {
siz[1] = n; build(get(1));
for (int i = 1; i <= m; i++) {
int x, y, z;
scanf("%d %d %d", &x, &y, &z);
for (auto j : anc[z])
if (j.x >= x && j.y <= y) {
q[j.z].push_back({x, y, i, 1});
break;
}
}
memset(mn, 0x3f, sizeof(mn));
for (int i = 1; i <= n; i++) {
sort(q[i].begin(), q[i].end(),
[](ev x, ev y) -> bool { return x.x == y.x ? x.t < y.t : x.x > y.x ;});
for (auto j : q[i]) {
if (j.t == 1) ans[j.z] = query(j.y);
else if (j.y < mn[j.z]) {
if (mn[j.z] <= n) upd(mn[j.z], -1);
upd(mn[j.z] = j.y, 1);
}
}
for (auto j : q[i]) {
if (!j.t) {
mn[j.z] = 1e9;
for(int p = j.y; p <= n; p += lowbit(p)) v[p] = 0;
}
}
}
for(int i = 1; i <= m; i++) printf("%d\n", ans[i]);
}
int main() {
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &cl[i]);
for(int i = 1; i < n; i++) {
int u, v; scanf("%d %d", &u, &v);
e[v].push_back(u), e[u].push_back(v);
}
solve();
}
P7735
一个节点最多连接两条黑边,那就记录它到重儿子的边是否是黑的、以及记录它的黑轻儿子集合。这里把贡献放到上面的点处计算。
在一整条重链上只要全覆盖就行了。轻重链切换时才有细节。
另一个做法是,把被覆盖的点全部染成一个新颜色,这样,一条边是黑色,当且仅当其两端点颜色相同。
同样树剖,线段树维护答案的同时维护左右端点的颜色,合并显然。
边点互换,非常巧妙的拆贡献。
还可以 LCT,放弃。
#include <bits/stdc++.h>
struct SGT {
struct node {
int l, r, s;
node(int l = -1, int r = -1, int s = 0) : l(l), r(r), s(s) {}
node(int l, int s) : l(l), r(l), s(s) {}
node operator + (const node &t) const {
if (r == -1) return t;
if (t.r == -1) return *this;
return {l, t.r, s + t.s + (r == t.l ? 1 : 0)};
}
node flip() {
return {r, l, s};
}
};
std::vector<node> s;
std::vector<int> laz;
int n;
SGT(int n) : n(n), s(4 * n), laz(4 * n, -1) {
std::function<void(int, int, int)> build = [&](int o, int l, int r) {
if (l == r) return s[o] = {l, l, 0}, void();
int mid = l + r >> 1;
build (o << 1, l, mid), build(o << 1 | 1, mid + 1, r);
s[o] = s[o << 1] + s[o << 1 | 1];
};
build(1, 0, n - 1);
}
void pushdown(int o, int l, int r) {
if (laz[o] == -1) return;
int mid = l + r >> 1;
s[o << 1] = {laz[o], laz[o], mid - l};
s[o << 1 | 1] = {laz[o], laz[o], r - mid - 1};
laz[o << 1] = laz[o << 1 | 1] = laz[o];
laz[o] = -1;
}
void mdf(int o, int l, int r, int x, int y, int t) {
if (x <= l && r <= y) return s[o] = node(t, t, r - l), laz[o] = t, void();
int mid = l + r >> 1; pushdown(o, l, r);
if (x <= mid) mdf(o << 1, l, mid, x, y, t);
if (y > mid) mdf(o << 1 | 1, mid + 1, r, x, y, t);
s[o] = s[o << 1] + s[o << 1 | 1];
}
node qry(int o, int l, int r, int x, int y) {
if (x <= l && r <= y) return s[o];
int mid = l + r >> 1; pushdown(o, l, r);
node ans;
if (x <= mid) ans = ans + qry(o << 1, l, mid, x, y);
if (y > mid) ans = ans + qry(o << 1 | 1, mid + 1, r, x, y);
return ans;
}
void mdf(int x, int y, int t) { /*printf("mdf %d %d\n", x, y);*/ mdf(1, 0, n - 1, x, y, t); }
node qry(int x, int y) { return /*printf("qry %d %d\n", x, y),*/ qry(1, 0, n - 1, x, y); }
};
void solve() {
int n, m; scanf("%d %d", &n, &m);
std::vector<std::vector<int>> e(n);
for (int i = 1, u, v; i < n; i++) {
scanf("%d %d", &u, &v), --u, --v;
e[u].push_back(v), e[v].push_back(u);
}
SGT s(n);
std::vector<int> top(n), hson(n, -1), siz(n, 1), fa(n), dep(n), dfn(n);
int dfc = 0;
{
std::function<void(int, int)> dfs1 = [&](int u, int f) {
fa[u] = f;
for (auto v : e[u]) if (v != f) {
dep[v] = dep[u] + 1, dfs1(v, u), siz[u] += siz[v];
if (hson[u] == -1 || siz[v] > siz[hson[u]])
hson[u] = v;
}
};
dfs1(0, 0);
std::function<void(int, int)> dfs2 = [&](int u, int t) {
top[u] = t, dfn[u] = dfc++;
if (hson[u] != -1) dfs2(hson[u], t);
for (auto v : e[u]) if (v != fa[u] && v != hson[u])
dfs2(v, v);
};
dfs2(0, 0);
}
int cnt = n;
while (m--) {
int op, x, y; scanf("%d %d %d", &op, &x, &y);
--x, --y;
// printf("%d -> %d\n", x, y);
if (op == 1) {
int tx = top[x], ty = top[y];
while (tx != ty) {
if (dep[tx] > dep[ty])
s.mdf(dfn[tx], dfn[x], cnt), x = fa[tx];
else
s.mdf(dfn[ty], dfn[y], cnt), y = fa[ty];
tx = top[x], ty = top[y];
}
if (dep[x] > dep[y]) std::swap(x, y);
s.mdf(dfn[x], dfn[y], cnt);
++cnt;
} else {
int tx = top[x], ty = top[y];
SGT::node xt, yt;
while (tx != ty) {
if (dep[tx] > dep[ty])
xt = s.qry(dfn[tx], dfn[x]) + xt, x = fa[tx];
else
yt = s.qry(dfn[ty], dfn[y]) + yt, y = fa[ty];
tx = top[x], ty = top[y];
}
if (dep[x] < dep[y])
yt = s.qry(dfn[x], dfn[y]) + yt;
else
xt = s.qry(dfn[y], dfn[x]) + xt;
printf("%d\n", (yt.flip() + xt).s);
}
}
}
int main() {
int T; scanf("%d", &T); while (T--) {
solve();
}
}
本文来自博客园,作者:purplevine,转载请注明原文链接:https://www.cnblogs.com/purplevine/p/18301088