CF375D - Tree and Queries
题意:给定一棵 (n(2leq nleq 10^5)) 个结点的树,根结点为 (1) 号结点,每个结点有颜色 (c(leq cleq 10^5)) 。然后 (m(1leq mleq 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
", ans[i]);
}
};
但是换一种思路,其实K没有必要作为其中一个维度,不过就不再维护curans,或者说,curans是一个数组,curans[i]表示频次大于等于i的颜色数。考虑一种颜色频次从cnt增加到cnt+1时,只会让大于等于cnt+1的答案+1,对于大于等于cnt其实是没有变化的。
这道题也可以树上启发式合并做。