【数据结构】树上莫队
CF375D - Tree and Queries
题意:给定一棵 \(n(2\leq n\leq 10^5)\) 个结点的树,根结点为 \(1\) 号结点,每个结点有颜色 \(c(\leq c\leq 10^5)\) 。然后 \(m(1\leq m\leq 10^5)\) 次询问,每次询问给两个参数 \((v,k)\) ,询问 \(v\) 子树内,频次至少有 \(k\) 次的颜色有多少种。
题解:经典计数颜色的数量,使用莫队算法。不过这一次颜色的数量 \(k\) 不确定,也要作为一个维度进行转移。这一次查询的并非树链而是子树,所以结点出栈的时候不需要删除这个结点的信息。对每个询问给一个区间 \([fst[v],lst[v]]\) 查询频次至少有 \(k\) 次的颜色有多少种。考虑带修改的莫队算法,块的大小为 \(\sqrt[3]{nk}\) ,总复杂度为 \(O(\sqrt[3]{n^4k})\) 。但是实测表明开 \(\sqrt[2]{n}\) 更快,不知道原因是什么,可能和这里的区间并非任意指定有关(是dfs序的区间)。
namespace MoAlgorithmOnTree {
static const int MAXM = 1e5 + 10;
static const int MAXN = 1e5 + 10;
static const int MAXC = 1e5 + 10;
int n, m, BLOCK;
vector<int> G[MAXN];
int id[MAXN], idt, fst[MAXN], lst[MAXN];
void dfs(int u, int p) {
id[++idt] = u;
fst[u] = lst[u] = idt;
for (int v : G[u]) {
if (v == p)
continue;
dfs(v, u);
lst[u] = lst[v];
}
}
struct Node {
int l, r, k, id;
bool operator<(const Node &node) const {
if (l / BLOCK != node.l / BLOCK)
return l < node.l;
if (r / BLOCK != node.r / BLOCK)
return r < node.r;
return k < node.k;
}
} node[MAXM];
ll ans[MAXM];
int L, R, K;
ll curans;
int c[MAXN], cnt[MAXC], sum[MAXN];
void add(int x) {
--sum[cnt[c[id[x]]]];
++cnt[c[id[x]]];
++sum[cnt[c[id[x]]]];
if (cnt[c[id[x]]] == K)
++curans;
}
void sub(int x) {
if (cnt[c[id[x]]] == K)
--curans;
--sum[cnt[c[id[x]]]];
--cnt[c[id[x]]];
++sum[cnt[c[id[x]]]];
}
void addK() {
curans -= sum[K];
++K;
}
void subK() {
--K;
curans += sum[K];
}
void Solve() {
ms(cnt), ms(sum);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
G[i].clear();
for (int i = 1; i <= n; ++i)
scanf("%d", &c[i]);
for (int i = 1; i <= n - 1; ++i) {
int u, v;
scanf("%d%d", &u, &v);
G[u].eb(v);
G[v].eb(u);
}
idt = 0;
dfs(1, 0);
BLOCK = (int)ceil(pow(n, 0.667));
for (int i = 1; i <= m; ++i) {
int v, k;
scanf("%d%d", &v, &k);
node[i].l = fst[v];
node[i].r = lst[v];
node[i].k = k;
node[i].id = i;
}
sort(node + 1, node + 1 + m);
L = 1, R = 0, K = 0, curans = n, sum[0] = n;
for (int i = 1; i <= m; ++i) {
while (L > node[i].l)
--L, add(L);
while (R < node[i].r)
++R, add(R);
while (L < node[i].l)
sub(L), ++L;
while (R > node[i].r)
sub(R), --R;
while (K < node[i].k)
addK();
while (K > node[i].k)
subK();
ans[node[i].id] = curans;
}
for (int i = 1; i <= m; ++i)
printf("%lld\n", ans[i]);
}
};
但是换一种思路,其实K没有必要作为其中一个维度,不过就不再维护curans,或者说,curans是一个数组,curans[i]表示频次大于等于i的颜色数。考虑一种颜色频次从cnt增加到cnt+1时,只会让大于等于cnt+1的答案+1,对于大于等于cnt其实是没有变化的。
这道题也可以树上启发式合并做。