LGP7880 [Ynoi 2006] rldcot 学习笔记

LGP7880 [Ynoi 2006] rldcot 学习笔记

Luogu Link

前言

本题解主要参考:这篇

题意简述

给定一棵 \(n\) 个结点的有根树。定义一个点的深度 \(\text{dep}_u\)\(u\) 到根路径上的边权和。\(m\) 次询问,每次给定一个区间 \([l,r]\),问对于所有的 \(l\le i\le j\le r\)\(\text{dep}_{\text{lca}(i,j)}\) 组成的集合中数值的种数。

\(n\le 10^5,m\le 5\times 10^5,|d|\le 10^9\)

做法解析

下文中,\(u\) 的“儿子树”指 \(u\) 的儿子 \(v\) 的子树。讨论的所有点对 \((i,j)\) 均满足 \(i\le j\)

首先我们发现,这道题等价于一个 \(\text{2-side}\) 矩形数颜色问题。什么意思呢?我们考虑我们把所有 \((i,j)\)\(i\) 为横坐标,\(j\) 为纵坐标摊在平面上,此时一个形如 \([l,r]\) 的询问实际上就是在问满足 \(x\ge l,y\le r\)\((x,y)\) 数量。\(\text{2-side}\) 指的是所有询问的矩形有两条边没贴着边线,或者说所有询问的矩形都是一个二维的缀(在这题就是右下角矩形)。

这个东西我们怎么做呢?我们从右往左扫整个平面,维护每种颜色的点当前最低在哪个 \(y\) 坐标能取到,让树状数组在这个 \(y\) 处保持这个颜色的 \(1\) 贡献。对于一个询问 \([x,y]\) 就是 \(x\) 坐标到了直接查树状数组中 \([1,y]\) 即可。

但是所有点对的数量级是 \(n^2\) 的,令人谔谔。

然而,接下来我们将会证得,真正需要考虑的点对只有 \(n\log n\) 级别!

考虑一个情景:\(\exists\,a<c<d<b\) 满足 \(\text{lca}(a,b)=\text{lca}(c,d)=u\)。那么这个时候你发现 \((a,b)\) 这个点对就是没用的,因为一个包含 \((a,b)\) 的区间也一定包含 \((c,d)\),我只保留 \((c,d)\) 就足以让我统计到这份答案了。

这样,我们称 \((c,d)\) 支配了 \((a,b)\)。进一步我们定义一个点对是支配对当且仅当其不被支配。所有支配对构成的集合在此问题上等效于原来那个大小为 \(n^2\) 的集合。

我们得把这些支配对找出来,考虑其判定。经典地,我们在 \(\text{lca}\) 处统计。当一个点对 \((u,v)\) 满足 \(\text{lca}(u,v)=x\) 时,首先 \(u,v\) 要在 \(x\) 的不同儿子树。然后,记 \(T\)\(x\) 的儿子树中 \(u\) 所在儿子树外的点构成的集合,则 \(v\) 必须是 \(T\)\(u\) 在结点编号上的前驱或后继(当然,还有一种情况是 \(u=v=x\))。

这个东西实则是个树上启发式合并的形式。说到这各位想必豁然开朗了:树上启发式合并来了,时间复杂度就有救了。

根据树上启发式合并的性质,一个点只会被遍历 \(\log n\) 次,每次遍历的时候会找前驱和后继,增加的点对是 \(O(1)\) 的。所以最后我们得到的“支配对”集合大小就是 \(O(n\log n)\) 的,非常可以接受!不过实际上,“一个点对被选进这个集合”是“这个点对是支配对”的必要不充分条件,因为我们在更新过程中是一个一个加儿子的子树的,可能存在一个点对在考虑前若干儿子树时是支配对,但是被后来的儿子树里的某个点支配。

现在二维矩形数颜色复杂度就降到 \(O(n\log^2n)\) 了!我们这个题就做完了。

代码实现

#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=1e5+5;
int N,M,X,Y,Z;struct edge{int v,w;};vector<edge> Tr[MaxN];
void addudge(int u,int v,int w){
    Tr[u].push_back({v,w});
    Tr[v].push_back({u,w});
}
int siz[MaxN],hson[MaxN],tfa[MaxN];lolo dep[MaxN];
void dfs1(int u,int f){
    siz[u]=1,tfa[u]=f;
    for(auto [v,w] : Tr[u]){
        if(v==f)continue;dep[v]=dep[u]+w,dfs1(v,u);
        siz[u]+=siz[v];if(siz[v]>siz[hson[u]])hson[u]=v;
    }
}
lolo D[MaxN];int C[MaxN],nln,ans[MaxN];
struct anob{int a,b;};int pos[MaxN];
set<int> S;vector<anob> U[MaxN],Q[MaxN];
int gpre(int x){auto ite=S.lower_bound(x);return ite==S.begin()?0:*(--ite);}
int gnxt(int x){auto ite=S.upper_bound(x);return ite==S.end()?0:*ite;}
void afind(int u,int c){
    int t;
    t=gpre(u);if(t)U[t].push_back({u,c});
    t=gnxt(u);if(t)U[u].push_back({t,c});
}
void calc(int u,int c){afind(u,c);for(auto [v,w] : Tr[u])if(v!=tfa[u])calc(v,c);}
void radd(int u){S.insert(u);for(auto [v,w] : Tr[u])if(v!=tfa[u])radd(v);}
void dfs2(int u,int op){
    for(auto [v,w] : Tr[u])if(v!=tfa[u]&&v!=hson[u])dfs2(v,1);
    if(hson[u]){dfs2(hson[u],0);};S.insert(u);
    for(auto [v,w] : Tr[u])if(v!=tfa[u]&&v!=hson[u])calc(v,C[u]),radd(v);
    if(op)S.clear();
}
struct BinidTree{
    int n,t[MaxN];
    void init(int x){n=x,fill(t,t+n+1,0);}
    int lowbit(int x){return x&(-x);}
    void add(int p,int x){for(;p<=n;p+=lowbit(p))t[p]+=x;}
    int gts(int p){int res=0;for(;p;res+=t[p],p-=lowbit(p));return res;}
}BiT;
int main(){
    readis(N,M),BiT.init(N);
    for(int i=1;i<N;i++)readis(X,Y,Z),addudge(X,Y,Z);
    dfs1(1,0);for(int i=1;i<=N;i++)D[i]=dep[i];
    sort(D+1,D+N+1),nln=unique(D+1,D+N+1)-(D+1);
    for(int i=1;i<=N;i++)C[i]=lwberi(D,nln,dep[i]);
    dfs2(1,0);for(int i=1;i<=nln;i++)pos[i]=N+1;
    for(int i=1;i<=N;i++)U[i].push_back({i,C[i]});
    for(int i=1;i<=M;i++)readis(X,Y),Q[X].push_back({Y,i});
    for(int i=N;i>=1;i--){
        for(auto [x,c] : U[i])BiT.add(pos[c],-1),minner(pos[c],x),BiT.add(pos[c],1);
        for(auto [x,id] : Q[i])ans[id]=BiT.gts(x);
    }
    for(int i=1;i<=M;i++)writil(ans[i]);
    return 0;
}

后记

树上启发式合并得了MVP!矩形数颜色就是躺赢狗!后面忘了。

posted @ 2025-05-14 13:30  矞龙OrinLoong  阅读(16)  评论(0)    收藏  举报