题意 : 给一个n个点的有根树(1号点为根) ,再给一个n个数组成的排列p, 共q次询问, 每次询问给一个l,r,x, 询问以x为根的子树里是否存在一个数出现在p[l, r] 中。
解法1 : 因为是树上单点询问所以, 很直接可以想到去做 树上启发式合并(不会的可以左转教程), 结合树状数组 比较暴力地离线回答, 时间复杂度 O(nlog2n)。虽然是2log的,但是因为两个log的来源常数都很小, 所以跑得飞快。
参考代码 :
struct BIT {
std::vector<int> a;
BIT(int n) : a(n) {}
void add(int i, int x) {
for (; i < a.size(); i += i & -i)
a[i] += x;
}
int getPos(int i) {
int res = 0;
for (; i; i -= i & -i)
res += a[i];
return res;
}
int getRange(int l, int r) {
return getPos(r) - getPos(l - 1);
}
};
template <class T>
struct Tree {
std::vector<std::vector<T>> &e;
std::vector<int> size, big;
Tree(std::vector<std::vector<T>> &g)
: e(g), big(g.size()), size(g.size()) {
dfs1(1, 0);
}
void dfs1(int x, int f) {
size[x] = 1;
for (int y : e[x])
if (y != f) {
dfs1(y, x);
size[x] += size[y];
if (size[y] > size[big[x]])
big[x] = y;
}
}
};
void solve() {
int n, qn;
std::cin >> n >> qn;
std::vector e(n + 1, std::vector<int>());
for (int u, v, i = 1; i < n; i++) {
std::cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
std::vector<int> p(n + 1), ip(n + 1);
for (int i = 1; i <= n; i++) {
std::cin >> p[i];
ip[p[i]] = i;
}
std::vector q(n + 1, std::vector<std::array<int, 3>>());
std::vector<int> ans(qn + 1);
for (int l, r, x, i = 1; i <= qn; i++) {
std::cin >> l >> r >> x;
q[x].push_back({l, r, i});
}
BIT t(n + 1);
Tree<int> G(e);
std::function<void(int, int, int op)> add = [&](int x, int fa, int op) {
t.add(ip[x], op);
for (int y : e[x]) {
if (y != fa)
add(y, x, op);
}
};
std::function<void(int, int)> dfs = [&](int x, int fa) {
for (int y : e[x]) {
if (y != fa and y != G.big[x])
dfs(y, x);
}
if (G.big[x]) {
dfs(G.big[x], x);
}
for (int y : e[x]) {
if (y != fa and y != G.big[x])
add(y, x, 1);
}
t.add(ip[x], 1);
for (auto [l, r, id] : q[x]) {
ans[id] = t.getRange(l, r);
}
if (x == G.big[fa])
return;
add(x, fa, -1);
};
dfs(1, 0);
for (int i = 1; i <= qn; i++) {
std::cout << (ans[i] ? "YES\n" : "NO\n");
}
std::cout << '\n';
}
解法2 :当我们看到1e5的 n 和3s的时限,或许没有什么比莫队更暴力了,于是莫队出现了,我们求出树的dfn序列, 然后在dfn上对于每次询问x,其实就是在询问 [dfn[x], dfn[x] + size[x] - 1] ,然后做法和树上启发式合并非常类似,这个时候如果你还是用树状数组维护, 或许会TLE在test33,为什么会这样?感性上的理解是,根号数据结构和树形数据结构难以兼容,会跑得慢,理性的理解就是有 n*sqrt(q) 次修改,每次修改都是logn的,复杂度自然就来到了 n*sqrt(q)*logn,所以如果代码的常数并不是很小,很有TLE的风险, 怎么办?当然是请出我们的根号数据结构 :分块!我们只需要做到O(1)单点加,O(sqrt(n))区间查询,即可完美兼容莫队,总时间复杂度:O((n*sqrt(q)+q*sqrt(n)) 轻松通过。
参考代码 :
struct Node { // 询问类
int l, r, id, tl, tr;
static constexpr int B = 400;
bool operator<(const Node &a) {
if (l / B != a.l / B)
return l < a.l;
return (l / B % 2 == 0 ? r < a.r : r > a.r);
}
};
template <class T>
struct BlockArray {
std::vector<T> a, sum;
const int n, B;
BlockArray(int n) : n(n), B(std::__lg(int(sqrt(n + 1)))), a(n) {
sum.assign((n - 1 >> B) + 1, 0);
}
void add(int i, T x) {
a[i] += x;
sum[i >> B] += x;
}
T getRange(int l, int r) {
T res = T();
if ((l >> B) == (r >> B)) {
for (int i = l; i <= r; i++)
res += a[i];
} else {
for (int i = l; (i >> B) == (l >> B); i++)
res += a[i];
for (int i = r; (i >> B) == (r >> B); i--)
res += a[i];
for (int j = (l >> B) + 1; j < (r >> B); j++)
res += sum[j];
}
return res;
}
};
void solve() {
int n, qn;
std::cin >> n >> qn;
std::vector e(n + 1, std::vector<int>());
for (int u, v, i = 1; i < n; i++) {
std::cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
std::vector<int> p(n + 1, 0), ip(n + 1, 0);
for (int i = 1; i <= n; i++) {
std::cin >> p[i];
ip[p[i]] = i;
}
std::vector<int> dfn(n + 1, 0), size(n + 1, 0);
std::vector<int> idfn(n + 1, 0);
int tot = 0;
std::function<void(int, int)> dfs = [&](int x, int fa) {
dfn[x] = ++tot;
idfn[tot] = x;
size[x] = 1;
for (int y : e[x]) {
if (y != fa) {
dfs(y, x);
size[x] += size[y];
}
}
};
dfs(1, 0);
std::vector<Node> q(qn);
std::vector<int> ans(qn + 1);
for (int x, i = 0; i < qn; i++) {
std::cin >> q[i].tl >> q[i].tr >> x;
q[i].id = i + 1;
q[i].l = dfn[x];
q[i].r = dfn[x] + size[x] - 1;
}
std::sort(q.begin(), q.end());
BlockArray<int> b(n + 1);
int l = 1, r = 0;
for (int i = 0; i < qn; i++) {
while (r < q[i].r)
b.add(ip[idfn[++r]], 1);
while (l > q[i].l)
b.add(ip[idfn[--l]], 1);
while (r > q[i].r)
b.add(ip[idfn[r--]], -1);
while (l < q[i].l)
b.add(ip[idfn[l++]], -1);
ans[q[i].id] = b.getRange(q[i].tl, q[i].tr);
}
for (int i = 1; i <= qn; i++) {
std::cout << (ans[i] ? "YES\n" : "NO\n");
}
std::cout << '\n';
}
解法3 : 如果你觉得以上两个做法时间复杂度还是不够优雅,这里是O(nlogn)的做法,线段树合并!我们只需要离线询问,然后树上每个点建一个线段树,然后dfs合并,然后每次回答做一次线段树区间查询即可。 虽然这是O(nlogn)的做法, 但是因为笔者的线段树是大常数的,所以跑的并不快。
参考代码:
struct Node {
int val = 0;
Node *l = nullptr;
Node *r = nullptr;
};
void up(Node *p) {
if (!p->l or !p->r) {
p->val = (p->l ? p->l->val : p->r->val);
} else
p->val = (p->l->val + p->r->val);
}
void insert(Node *&p, int l, int r, int x) {
if (!p)
p = new Node();
if (l == r) {
p->val++;
return;
}
int mid = l + r >> 1;
if (x <= mid)
insert(p->l, l, mid, x);
else
insert(p->r, mid + 1, r, x);
up(p);
}
Node *merge(Node *x, Node *y, int l, int r) {
if (!x or !y)
return (x ? x : y);
if (l == r) {
x->val += y->val;
return x;
}
int mid = l + r >> 1;
x->l = merge(x->l, y->l, l, mid);
x->r = merge(x->r, y->r, mid + 1, r);
return up(x), x;
}
int get(Node *p, int l, int r, int tl, int tr) {
if (!p)
return 0;
if (tl <= l and r <= tr) {
return p->val;
}
int mid = l + r >> 1;
return (tl <= mid ? get(p->l, l, mid, tl, tr) : 0) +
(mid < tr ? get(p->r, mid + 1, r, tl, tr) : 0);
}
void solve() {
int n, qn;
std::cin >> n >> qn;
std::vector e(n + 1, std::vector<int>());
for (int u, v, i = 1; i < n; i++) {
std::cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
std::vector<int> p(n + 1, 0), ip(n + 1, 0);
for (int i = 1; i <= n; i++) {
std::cin >> p[i];
ip[p[i]] = i;
}
std::vector q(n + 1, std::vector<std::array<int, 3>>());
std::vector<int> ans(qn + 1, 0);
for (int l, r, x, i = 1; i <= qn; i++) {
std::cin >> l >> r >> x;
q[x].push_back({l, r, i});
}
std::vector<Node *> root(n + 1);
std::function<void(int, int)> dfs = [&](int x, int fa) {
root[x] = new Node();
insert(root[x], 1, n, ip[x]);
for (int y : e[x]) {
if (y != fa) {
dfs(y, x);
root[x] = merge(root[x], root[y], 1, n);
}
}
for (auto [l, r, id] : q[x]) {
ans[id] = get(root[x], 1, n, l, r);
}
};
dfs(1, 0);
for (int i = 1; i <= qn; i++) {
std::cout << (ans[i] ? "YES\n" : "NO\n");
}
std::cout << '\n';
}
解法4 :什么?你觉得离线还不够优雅,问有没有在线的O(nlogn)的做法?那就是主席树啦!你只需要对树求出dfn序列,然后求出排列的逆变换ip数组,然后如下求出b数组:
for (int i = 1; i <= n; i++) {
b[dfn[i]] = ip[i];
}
那么每次询问, 你只需要在b数组里,下标为[dfn[x], dfn[x] + size[x] - 1]的范围内找到是否存在值域在[l, r]区间里的数即可,恰好主席树就可以做到这样的事。 由于笔者的主席树的写法并不常规,导致常数仍然很大,所以时间上还是跑不过前两种解法。
参考代码:
template <class T>
struct PresidentTree {
struct Node {
Node *l = nullptr;
Node *r = nullptr;
int val = 0;
void extend() {
if (!l) l = new Node();
if (!r) r = new Node();
}
};
const T Min, Max;
std::vector<Node *> root;
PresidentTree(const std::vector<T> &a)
: root(a.size()),
Min(*std::min_element(a.begin() + 1, a.end())),
Max(*std::max_element(a.begin() + 1, a.end())) {
root[0] = new Node();
for (int i = 1; i < a.size(); i++) {
modify(root[i], Min, Max, a[i]);
root[i] = merge(root[i], root[i - 1], Min, Max);
}
}
void up(Node *p) {
if (!p->l or !p->r) {
p->val = (p->l ? p->l->val : p->r->val);
} else
p->val = p->l->val + p->r->val;
}
void modify(Node *&p, T l, T r, T x) {
if (!p)
p = new Node();
if (l == r) {
p->val++;
return;
}
auto mid = l + r >> 1;
if (x <= mid)
modify(p->l, l, mid, x);
else
modify(p->r, mid + 1, r, x);
up(p);
}
Node *merge(Node *x, Node *y, T l, T r) {
if (!x or !y)
return (x ? x : y);
Node *p = new Node();
if (l == r) {
p->val = x->val + y->val;
return p;
}
auto mid = l + r >> 1;
p->l = merge(x->l, y->l, l, mid);
p->r = merge(x->r, y->r, mid + 1, r);
return up(p), p;
}
int getRange(int l, int r, T tl, T tr) {
return getRange(root[l - 1], root[r], Min, Max, tl, tr);
}
int getRange(Node *x, Node *y, T l, T r, T tl, T tr) {
x->extend(), y->extend();
if (tl <= l and r <= tr) {
return y->val - x->val;
}
auto mid = l + r >> 1;
return (tl <= mid ? getRange(x->l, y->l, l, mid, tl, tr) : 0) +
(mid < tr ? getRange(x->r, y->r, mid + 1, r, tl, tr) : 0);
}
T getKth(int l, int r, int k) {
return getKth(root[l - 1], root[r], Min, Max, k);
}
T getKth(Node *x, Node *y, T l, T r, int k) {
if (l == r)
return l;
auto mid = l + r >> 1;
x->extend(), y->extend();
int L = y->l->val - x->l->val;
return (L >= k ? getKth(x->l, y->l, l, mid, k)
: getKth(x->r, y->r, mid + 1, r, k - L));
}
};
template <class T>
struct Tree {
std::vector<std::vector<T>> &e;
std::vector<int> size, big, dep, tp, fa, dfn;
int n, tot = 0;
Tree(std::vector<std::vector<T>> &g)
: e(g), n(g.size() - 1), tp(g.size()), big(g.size()),
size(g.size()), dep(g.size()), fa(g.size()), dfn(g.size()) {
dfs1(1, 0);
dfs2(1, 1);
}
void dfs1(int x, int f) {
dep[x] = dep[f] + 1;
size[x] = 1;
fa[x] = f;
for (int y : e[x])
if (y != f) {
dfs1(y, x);
size[x] += size[y];
if (size[y] > size[big[x]])
big[x] = y;
}
}
void dfs2(int x, int top) {
dfn[x] = ++tot;
tp[x] = top;
if (big[x])
dfs2(big[x], top);
for (int y : e[x])
if (y != big[x] and y != fa[x])
dfs2(y, y);
}
};
void solve() {
int n, qn;
std::cin >> n >> qn;
std::vector e(n + 1, std::vector<int>());
for (int u, v, i = 1; i < n; i++) {
std::cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
std::vector<int> p(n + 1), ip(n + 1);
for (int i = 1; i <= n; i++) {
std::cin >> p[i];
ip[p[i]] = i;
}
Tree<int> G(e);
std::vector<int> b(n + 1, 0);
for (int i = 1; i <= n; i++) {
b[G.dfn[i]] = ip[i];
}
PresidentTree<int> pt(b);
int l, r, x;
while (qn--) {
std::cin >> l >> r >> x;
int res = pt.getRange(G.dfn[x], G.dfn[x] + G.size[x] - 1, l, r);
std::cout << (res > 0 ? "YES\n" : "NO\n");
}
std::cout << '\n';
}
感谢您阅读至此,求个赞。