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;
}

posted @ 2026-09-27 16:22  wmq2012  阅读(24)  评论(0)    收藏  举报