AT_abc477_g 题解
笔记列表
AT_abc477_g 题解
\(\text{做法}\)
树上莫队+值域分块。
\(\text{题意}\)
给定一棵树,每个点有一个颜色,多次询问一条路径上,有多少种颜色的出现次数在给定范围内。
\(\text{思路}\)
在树上求欧拉序。
对每一个节点记录它的进入时间 \(st_u\) 和结束时间 \(ed_u\)。
在查询时,对于 \(u\) 到 \(v\) 的路径,不妨 \(st_u<st_v\)。
设 \(lca(u,v)=w\)。
- 当 \(u = w\) 时,那么在欧拉序上的答案是 \([st_u,st_v]\)。
- 当 \(u \ne w\) 时,在欧拉序上的答案是 \([ed_u,st_v]\),加上 \(w\) 这个点的贡献。
注意,这里在欧拉序上做莫队时,当一个点的 \(st,ed\) 都在查询区间范围内,那么这个点没有贡献。
- 当且仅当一个点恰好有一个出现位置在区间内时,它对路径有贡献;
- 两个出现位置都在区间内会抵消;
- 两个都不在则无贡献。
在这个前提下可以发现查询区间为什么这样取。
- 当 \(u \ne w\) 时,我们取了 \([ed_u,st_v]\),那么有:
- 在 \(u\) 到 \(w\) 上的点(不含 \(w\)),只有结束时间处于范围内恰好算了一次。
- 在 \(w\) 到 \(v\) 上的点(不含 \(w\)),只有开始时间处于范围内算了一次。
- 而那些在 \(w\) 的子树内,却不在路径上的点,它们的开始时间和结束时间都被覆盖,计算两次,不会对答案产生贡献。
- 那么此时 \(u\) 到 \(v\) 路径上的点只有 \(w\) 没有在答案内,因为 \(st_w < st_u\),\(ed_w>ed_v\)。单独把 \(w\) 加入到区间,统计完答案在恢复。
- 当 \(u = w\) 时,可以发现情况是一样的,唯一的区别是从 \(st_u\) 开始已经包括到了两个点的 \(lca\),不需要额为统计。
那么我们可以用值域分块在莫队的移动过程中维护:
- \(tax_i\) 表示颜色 \(i\) 的出现次数;
- \(cnt_i\) 表示出现次数为 \(i\) 的有多少个颜色;
- 用分块加速 \(cnt\) 的求和过程。
为什么用值域分块不用值域线段树?
虽然线段树的查询 \(O(\log n)\) 比分块 \(O(\sqrt n)\) 快,但是莫队的修改次数要远大于查询次数,而分块的单点修改是 \(O(1)\) 的,所以选择分块。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fore(i,a,b) for( int i=(a); i<=(b); ++i)
#define repe(i,a,b) for( int i=(a); i>=(b); --i)
using namespace std;
const int N = 5e5 + 10;
const int LOG = 20;
int n, Q;
int a[N];
vector<int> G[N];
int dep[N];
int fa[N][LOG];
int st[N], ed[N], tim;
int eul[N];
struct Ques{
int l, r, id, lca, a, b;
}q[N];
void dfs(int u,int fat) {
fa[u][0] = fat;
dep[u] = dep[fat] + 1;
fore(i, 1, 19) fa[u][i] = fa[fa[u][i - 1]][i - 1];
st[u] = ++ tim;
eul[tim] = u;
for(int v : G[u]) {
if(v == fat) continue;
dfs(v, u);
}
ed[u] = ++ tim;
eul[tim] = u;
}
int lca(int u,int v) {
if(dep[u] < dep[v]) swap(u, v);
repe(i, 19, 0) {
if(dep[fa[u][i]] >= dep[v]) u = fa[u][i];
}
if(u == v) return u;
repe(i, 19, 0) {
if(fa[u][i] != fa[v][i]) {
u = fa[u][i], v = fa[v][i];
}
}
return fa[u][0];
}
struct Blocker {
int sum[N];
int tax[N];
int cnt[N];
int L[N], R[N];
int pos[N];
int len, num;
void init() {
int V = n;
len = sqrt(n + 1);
num = (n + 1 + len - 1) / len;
fore(i, 1, num) {
L[i] = (i - 1) * len;
R[i] = min(i * len - 1, n);
}
fore(i, 1, num) {
fore(j, L[i], R[i]) {
pos[j] = i;
}
sum[i] = 0;
}
fore(i, 0, n) cnt[i] = 0;
fore(i, 0, V) tax[i] = 0;
cnt[0] = V;
sum[pos[0]] = V;
}
void modify(int x,int y) {
sum[pos[tax[x]]] --;
cnt[tax[x]] --;
tax[x] += y;
cnt[tax[x]] ++;
sum[pos[tax[x]]] ++;
}
int query(int l,int r) {
int p = pos[l];
int q = pos[r];
int res = 0;
if(p == q) {
fore(i, l, r) {
res += cnt[i];
}
return res;
}
fore(i, l, R[p]) res += cnt[i];
fore(i, L[q], r) res += cnt[i];
fore(i, p + 1, q - 1) res += sum[i];
return res;
}
} Block;
int qlen;
bool cmp(Ques A, Ques B) {
int al = A.l / qlen;
int bl = B.l / qlen;
if(al != bl) return al < bl;
if(al & 1) return A.r < B.r;
return A.r > B.r;
}
int vis[N];
void Tow(int x) { // 这里记录一个vis,如果在查询区间有多次那么vis是0
if(vis[eul[x]] == 0) {
Block.modify(a[eul[x]], 1);
vis[eul[x]] = 1; // 如果已经有两次是删除变成一次,或者是零次变成一次,那这个点都有贡献
} else { // 从一次删除成一次或者从一次增加到两次都要扣除它的贡献
Block.modify(a[eul[x]], -1);
vis[eul[x]] = 0;
}
}
int ans[N];
signed main(){
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
cin >> n >> Q;
fore(i, 1, n) cin >> a[i];
fore(i, 2, n) {
int u, v;
cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1, 0);
Block.init();
fore(i, 1, Q) {
int u, v, p;
cin >> u >> v >> q[i].a >> q[i].b;
if(st[u] > st[v]) swap(u, v);
p = lca(u, v);
if(u == p) {
q[i].l = st[u]; q[i].r = st[v];
q[i].lca = 0;
} else {
q[i].l = ed[u]; q[i].r = st[v];
q[i].lca = p;
}
q[i].id = i;
}
qlen = sqrt(n * 2);
sort(q + 1, q + Q + 1, cmp);
int l = 1, r = 0;
fore(i, 1, Q) {
while(r < q[i].r) Tow(++ r);
while(l > q[i].l) Tow(-- l);
while(r > q[i].r) Tow(r --);
while(l < q[i].l) Tow(l ++);
if(q[i].lca) Tow(st[q[i].lca]);
ans[q[i].id] = Block.query(q[i].a, q[i].b);
if(q[i].lca) Tow(st[q[i].lca]);
}
fore(i, 1, Q) cout << ans[i] << '\n';
return 0;
}
本文来自博客园,作者:wmq2012,转载请注明原文链接:https://www.cnblogs.com/wmq2012/articles/23134056

浙公网安备 33010602011771号