A Simple Problem(路径)
题面
有一棵 \(n\) 个点的树,每个点有一个颜色,编号为 \(i\) 的点的颜色为 \(col[i]\)。
给定 \(m\) 组询问,每次询问给定 \(u,v,a,b\),表示询问在 \(u\) 到 \(v\) 的简单路径上,出现次数 \(\in[a,b]\) 的颜色有多少种。
限制
测试点编号 | 特殊限制 | 分值 |
---|---|---|
\(1\sim2\) | \(n,m\le 1000\) | \(10\) |
\(3\sim8\) | \(n,m\le 50000\) | \(30\) |
\(9\sim12\) | \(a=b\) | \(20\) |
\(13\sim16\) | \(b=n\) | \(20\) |
\(16\sim20\) | 无 | \(20\) |
对于 \(100\%\) 的数据,满足 \(n,m\le 2\times 10^5\),\(col[i]\le 10^9\),\(0\le a\le b\le n\)。
时间限制为 \(2s\)。
题解
10pts
每次询问让 \(u,v\) 暴力向上跳到 \(LCA\),在这个过程中统计颜色的出现次数。然后遍历所有颜色,统计答案即可。
离散化后颜色的值域是 \(O(n)\) 的,时间复杂度 \(O(nm)\)。
40pts
树上莫队:求出树的欧拉序(进出一个点时都要加入这个点的标号),记录进出时的时间戳分别为 \(L[x],R[x]\)。然后对于一条路径 \(u-LCA-v\),默认 \(L[u]\le L[v]\),我们进行分类讨论
- \(LCA=u\),这时路径上的点集为:在欧拉序下标为 \([L[u],L[v]]\) 之间,且只出现 \(1\) 次的点。
- \(LCA\not=u\),这时路径上的点集为:在欧拉序下标为 \([R[u],L[v]]\) 之间,且只出现 \(1\) 次的点。这样统计不到 \(LCA\),需要特判一个点。
然后我们就可以用莫队来维护路径信息。回到这题上,考虑维护出现次数,每次莫队区间增加/删除一个数,需要单点加/减,询问相当于一个区间和。用树状数组维护即可。
时间复杂度 \(O(n\sqrt{m}\log{n})\), 可以通过 \(n,m\le 50000\) 的测试点。不出意外的话,这个算法是卡不过去的
60pts
对于 \(a=b\) 的特殊性质,相当于询问出现次数为 \(a\) 的颜色数,直接在莫队转移的时候 \(O(1)\) 统计即可。时间复杂度 \(O(n\sqrt{m})\)。
结合前面的算法可以获得 \(\texttt{60pts}\)。
80pts
对于 \(b=n\) 的特殊性质,相当于询问出现次数 \(\ge a\) 的颜色数,这是对正解的一个提示。
设 \(ge[i]\) 表示出现次数 \(\ge i\) 的颜色数量,考虑到莫队算法每次只会有一种颜色的出现次数 \(\pm 1\) 的变化,我们可以直接 \(O(1)\) 维护这个信息:设 \(cnt(c)\) 表示当前颜色 \(c\) 的出现次数,则
- 颜色 \(c\) 出现次数增加 \(1\),则 \(ge[]\) 唯一的变化是 \(\texttt{ge[cnt(c)+1]++}\)
- 颜色 \(c\) 出现次数减少 \(1\),则 \(ge[]\) 唯一的变化是 \(\texttt{ge[cnt(c)]--}\)
细节:注意在莫队之前初始化 \(ge[0]\)。
这样的时间复杂度是 \(O(n\sqrt{m})\)。
100pts(算法一)
在 \(\texttt{80pts}\) 的解法中,我们已经可以 \(O(n\sqrt{m})\) 维护出现次数 \(\ge k\) 的信息,于是我们将每个询问 \([a, b]\) 看作出现 \(\ge a\) 减去出现 \(\ge b+1\) 即可,时间复杂度仍然是 \(O(n\sqrt{m})\)。
- 回想树状数组的那个解法,可以发现树状数组维护了前缀,而这个做法维护的是后缀。
100pts(算法二)
莫队的指针移动次数为 \(O(n\sqrt{m})\),而询问是 \(O(m)\) 的。所以我们可以考虑根号平衡:设计一个 \(O(1)\) 更新颜色出现次数,\(O(\sqrt{n})\) 查询出现次数 \(\ge k\) 的颜色数量的数据结构。(当然也可以查询 \(\le k\))
对出现次数分块,每次修改相当于一个单点 \(-1\) 和一个单点 \(+1\),同时维护块内和(代表出现次数在这个块中的颜色数),这些是可以 \(O(1)\) 做到的。
对于询问,我们 \(O(\sqrt{n})\) 跳到 \(k\) 对应块,然后暴力统计块内零散点,再暴力统计后面的整块即可,复杂度是 \(O(\sqrt{n})\) 的。
总时间复杂度 \(O(n\sqrt{m}+m\sqrt{n})\),期望得分 \(100\)。数据给的 \(n,m\) 同级,所以并没有卡这个算法的意思。
代码
std,$O(n\sqrt{m})$
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5, M = 5e5 + 5;
struct Query {
int id, l, r, a, b, lca;
} q[M];
int n, m, col[N], ans[M];
int num[N], numtot;
vector<int> adj[N];
int fa[N], son[N], siz[N], dep[N], top[N], L[N], R[N], id[N * 2], dfntot;
int len, bel[N * 2], cnt[N], ge[N * 2], tim[N];
void dfs1(int x, int f) {
fa[x] = f;
dep[x] = dep[f] + 1;
siz[x] = 1;
for(auto y : adj[x]) {
if(y != f) {
dfs1(y, x);
siz[x] += siz[y];
if(siz[y] > siz[son[x]]) {
son[x] = y;
}
}
}
}
void dfs2(int x, int t) {
top[x] = t;
L[x] = ++dfntot;
id[dfntot] = x;
if(son[x]) {
dfs2(son[x], t);
}
for(int y : adj[x]) {
if(y == fa[x] || y == son[x]) {
continue;
}
dfs2(y, y);
}
R[x] = ++dfntot;
id[dfntot] = x;
}
int LCA(int x, int y) { // 求LCA
while(top[x] != top[y]) {
if(dep[fa[top[x]]] < dep[fa[top[y]]]) {
swap(x, y);
}
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
void update(int x) {
// tim[x]表示点x当前出现1次还是2次
if(tim[x]) {
--ge[cnt[col[x]]]; // ge[i]表示出现次数 >= i 的颜色数量
--cnt[col[x]]; // cnt[i]表示颜色i的出现次数
} else {
++cnt[col[x]];
++ge[cnt[col[x]]];
}
tim[x] ^= 1;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; ++i) {
cin >> col[i];
num[++numtot] = col[i];
}
sort(num + 1, num + numtot + 1);
numtot = unique(num + 1, num + numtot + 1) - num - 1;
for(int i = 1; i <= n; ++i) {
col[i] = lower_bound(num + 1, num + numtot + 1, col[i]) - num; // 离散化
}
for(int i = 1; i < n; ++i) {
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
dfs1(1, 0);
dfs2(1, 1);
for(int i = 1; i <= m; ++i) {
int u, v, a, b;
cin >> u >> v >> a >> b;
if(L[u] > L[v]) {
swap(u, v);
}
int lca = LCA(u, v);
if(lca == u) {
q[i] = {i, L[u], L[v], a, b, 0};
} else {
q[i] = {i, R[u], L[v], a, b, lca};
}
}
// 莫队
len = max(1, int(2 * n / sqrt(m)));
for(int i = 1; i <= 2 * n; ++i) {
bel[i] = (i - 1) / len + 1;
}
sort(q + 1, q + m + 1, [&](auto lhs, auto rhs) {
return (bel[lhs.l] ^ bel[rhs.l]) ? bel[lhs.l] < bel[rhs.l] : ((bel[lhs.l] & 1) ? lhs.r < rhs.r : lhs.r > rhs.r);
});
int l = 1, r = 0;
ge[0] = numtot; // 出现次数 >= 0 的颜色有numtot种
for(int i = 1; i <= m; ++i) {
while(q[i].l < l) {
update(id[--l]);
}
while(r < q[i].r) {
update(id[++r]);
}
while(l < q[i].l) {
update(id[l++]);
}
while(q[i].r < r) {
update(id[r--]);
}
if(q[i].lca) {
update(q[i].lca);
}
ans[q[i].id] = ge[q[i].a] - ge[q[i].b + 1];
if(q[i].lca) {
update(q[i].lca);
}
}
for(int i = 1; i <= m; ++i) {
cout << ans[i] << "\n";
}
return 0;
}
40pts,$O(n\sqrt{m}\log{n})$
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
const int N = 5e5 + 5, M = 5e5 + 5;
struct Query {
int id, l, r, a, b, lca;
} q[M];
struct Fenwick {
int n, sum[N];
void add(int x, int v) {
++x;
while(x <= n) {
sum[x] += v;
x += x & -x;
}
sum[x] += v;
}
int ask(int x) {
++x;
int res = 0;
while(x) {
res += sum[x];
x -= x & -x;
}
return res;
}
int ask(int l, int r) {
return ask(r) - ask(l - 1);
}
} fen;
int n, m, col[N], ans[M];
int num[N], numtot;
vector<int> adj[N];
int fa[N], son[N], siz[N], dep[N], top[N], L[N], R[N], id[N * 2], dfntot;
int len, bel[N * 2], cnt[N], tim[N];
void dfs1(int x, int f) {
fa[x] = f;
dep[x] = dep[f] + 1;
siz[x] = 1;
for(auto y : adj[x]) {
if(y != f) {
dfs1(y, x);
siz[x] += siz[y];
if(siz[y] > siz[son[x]]) {
son[x] = y;
}
}
}
}
void dfs2(int x, int t) {
top[x] = t;
L[x] = ++dfntot;
id[dfntot] = x;
if(son[x]) {
dfs2(son[x], t);
}
for(int y : adj[x]) {
if(y == fa[x] || y == son[x]) {
continue;
}
dfs2(y, y);
}
R[x] = ++dfntot;
id[dfntot] = x;
}
int LCA(int x, int y) {
while(top[x] != top[y]) {
if(dep[fa[top[x]]] < dep[fa[top[y]]]) {
swap(x, y);
}
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
void update(int x) {
fen.add(cnt[col[x]], -1);
if(tim[x]) {
--cnt[col[x]];
} else {
++cnt[col[x]];
}
fen.add(cnt[col[x]], 1);
tim[x] ^= 1;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; ++i) {
cin >> col[i];
num[++numtot] = col[i];
}
sort(num + 1, num + numtot + 1);
numtot = unique(num + 1, num + numtot + 1) - num - 1;
for(int i = 1; i <= n; ++i) {
col[i] = lower_bound(num + 1, num + numtot + 1, col[i]) - num;
}
for(int i = 1; i < n; ++i) {
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
dfs1(1, 0);
dfs2(1, 1);
for(int i = 1; i <= m; ++i) {
int u, v, a, b;
cin >> u >> v >> a >> b;
assert(0 <= a && a <= b && b <= n);
if(L[u] > L[v]) {
swap(u, v);
}
int lca = LCA(u, v);
if(lca == u) {
q[i] = {i, L[u], L[v], a, b, 0};
} else {
q[i] = {i, R[u], L[v], a, b, lca};
}
}
len = max(1, int(2 * n / sqrt(m)));
for(int i = 1; i <= 2 * n; ++i) {
bel[i] = (i - 1) / len + 1;
}
sort(q + 1, q + m + 1, [&](auto lhs, auto rhs) {
return (bel[lhs.l] ^ bel[rhs.l]) ? bel[lhs.l] < bel[rhs.l] : ((bel[lhs.l] & 1) ? lhs.r < rhs.r : lhs.r > rhs.r);
});
fen.n = n + 1;
fen.add(0, numtot);
int l = 1, r = 0;
for(int i = 1; i <= m; ++i) {
while(q[i].l < l) {
update(id[--l]);
}
while(r < q[i].r) {
update(id[++r]);
}
while(l < q[i].l) {
update(id[l++]);
}
while(q[i].r < r) {
update(id[r--]);
}
if(q[i].lca) {
update(q[i].lca);
}
ans[q[i].id] = fen.ask(q[i].a, q[i].b);
if(q[i].lca) {
update(q[i].lca);
}
}
for(int i = 1; i <= m; ++i) {
cout << ans[i] << "\n";
}
return 0;
}
10pts,$O(nm)$
自己动手,丰衣足食。