支配对

P7880 rldcot
边有边权,\(m\) 次询问一个树上 \([l,r]\) 的节点区间内 \(dep_{lca(i,j)}\) 的种类数。
如果是 \(O(n^2)\) 个点对显然会有重复的信息,既然要求 \(l \le i \le j \le r\),那么对于 lca 相同的点对 \((a,b)\)\((c,d)\) 来说,若 \(a<c,d<b\)\([l,r]\) 包含 \((a,b)\) 就一定会包含 \((c,d)\),那么 \((a,b)\) 这个点对就没有用,称 \((c,d)\) 为支配对。
这类在树上与 lca 相关的支配对一般考虑 dsu on tree,以当前点 \(x\) 为 lca,去收集子树的信息,如果两个点在不同子树里 lca 就是 \(x\),那么按照支配对的思路,任何一个点 \(u\) 应该与之最编号接近的两个点组成点对,我们用一个 set,遍历完重儿子 set 里存好这个子树里的点,先遍历一个轻儿子子树,每个点找出前驱后继作为点对,再把这个子树也加入集合。
得到了 \(O(n \log n)\) 个点对,总时间复杂度 \(O(n \log^2 n)\),再扫描线就可以了。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int n,m;
const int N = 1e5+10;
struct edge{
    int v,w;
};
vector<edge>g[N];
struct qy{
    int l,r;
    ll id;
};
vector<qy>q,p;
bool cmp(qy a,qy b){
    return a.r<b.r;
}
ll dep[N],b[N],len;
int fa[N],siz[N],wc[N];
void dfs(int x,int f){
    fa[x]=f;
    siz[x]=1;
    for(auto [v,w]:g[x]){
        if(v==f)continue;
        dep[v]=dep[x]+w;
        dfs(v,x);
        siz[x]+=siz[v];
        if(siz[v]>siz[wc[x]])wc[x]=v;
    }
}
set<int>s;
int pre(int x){
    auto it=s.lower_bound(x);
    if(it==s.begin())return 0;
    it--;
    return *it;
}
int nxt(int x){
    auto it=s.upper_bound(x);
    if(it==s.end())return 0;
    return *it;
}
void DFS(int x,int f,int c){
    int e=pre(x);
    if(e)p.push_back({e,x,dep[c]});
    e=nxt(x);
    if(e)p.push_back({x,e,dep[c]});
    for(auto [v,w]:g[x]){
        if(v==f)continue;
        DFS(v,x,c);
    }
}
void DFS2(int x,int f,int c){
    s.insert(x);
    for(auto [v,w]:g[x]){
        if(v==f)continue;
        DFS2(v,x,c);
    }
}
void dot(int x,int f){
    for(auto [v,w]:g[x]){
        if(v==f||v==wc[x])continue;
        dot(v,x);
        s.clear();
    }
    if(wc[x])dot(wc[x],x);
    int e=pre(x);
    if(e)p.push_back({e,x,dep[x]});
    e=nxt(x);
    if(e)p.push_back({x,e,dep[x]});
    s.insert(x);
    for(auto [v,w]:g[x]){
        if(v==f||v==wc[x])continue;
        DFS(v,x,x);
        DFS2(v,x,x);
    }
}
int lowbit(int x){
    return x&(-x);
}
int t[N],lst[N],ans[5*N];
void add(int p,int v){
    for(int i=p;i<=n;i+=lowbit(i)){
        t[i]+=v;
    }
}
int ask(int p){
    int res=0;
    for(int i=p;i;i-=lowbit(i)){
        res+=t[i];
    }
    return res;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<n;i++){
        int u,v,w;
        cin>>u>>v>>w;
        g[u].push_back({v,w});
        g[v].push_back({u,w});
    }
    dfs(1,0);
    for(int i=1;i<=n;i++){
        b[++len]=dep[i];
    }
    sort(b+1,b+1+len);
    len=unique(b+1,b+1+len)-b-1;
    for(int i=1;i<=n;i++){
        dep[i]=lower_bound(b+1,b+1+len,dep[i])-b;
    }
    for(int i=1;i<=m;i++){
        int l,r;
        cin>>l>>r;
        q.push_back({l,r,i});
    }
    dot(1,0);
    for(int i=1;i<=n;i++){
        p.push_back({i,i,dep[i]});
    }
    sort(p.begin(),p.end(),cmp);
    sort(q.begin(),q.end(),cmp);
    int cur=0;
    for(auto [l,r,id]:q){
        while(cur<p.size()&&p[cur].r<=r){
            int col=p[cur].id;
            if(!lst[col]){
                lst[col]=p[cur].l;
                add(lst[col],1);
            }else if(p[cur].l>lst[col]){
                add(lst[col],-1);
                lst[col]=p[cur].l;
                add(lst[col],1);
            }
            cur++;
        }
        ans[id]=ask(r)-ask(l-1);
    }
    for(int i=1;i<=m;i++){
        cout<<ans[i]<<endl;
    }
    return 0;
}

[Ynoi2004] rpmtdq
这题询问编号区间 \([l,r]\) 中距离最小的两点距离,与树上的路径相关,想到点分治,因为一条路径必定会经过一个分治中心,设当前分治层内节点 \(u\) 到中心的距离为 \(d_u\),那么 \(u,v\) 两点间的距离可以表示为 \(d_u+d_v\),尽管 \(u,v\) 在同一个子树内时这个距离就不是真实距离,但是我们能在之后的分治层中保证他是真实距离。
考虑怎么样的点对会成为支配对,对于 \(i < j< k\),如果 \(d_j<d_k\),那么显然 \(k\) 就被支配了,那如果 \(d_j>d_k\) 呢?可能 \((j,k)\) 会作为支配对把 \((j,i)\) 支配,也可能这两对都能贡献答案。
每一层可以得到很多二元组 \((i,d_i)\),按 \(i\) 排序,用单调栈处理出每一个 \(i\) 前面第一个比他小的数和后面第一个比他小的数,作为可贡献的点对加入最终集合中,那么每一层 \(O(n)\) 个点对,总共就是 \(O(n \log n)\) 个点对。
老样子和询问一起做扫描线,取一个后缀最小值就好了。
复杂度 \(O(n \log^2 n+q \log n)\)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define _int __int128
#define ull unsigned long long
#define pii pair<int,ll>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int n,m;
const int N = 2e5+10;
const ll inf=1e17;
struct edge{
    int v;
    ll w;
};
vector<edge>g[N];
struct qy{
    int l,r;
    ll id;
};
vector<qy>q,p;
bool cmp(qy a,qy b){
    return a.r<b.r;
}
ll dep[N],fa[N],siz[N],wc[N],top[N],vis[N],maxp[N],tot,rt;
ll d[N];
void dfs(int x,int f){
    dep[x]=dep[f]+1;
    fa[x]=f;
    siz[x]=1;
    for(auto [v,w]:g[x]){
        if(v==f)continue;
        d[v]=d[x]+w;
        dfs(v,x);
        siz[x]+=siz[v];
        if(siz[v]>siz[wc[x]])wc[x]=v;
    }
}
void dfs2(int x,int t){
    top[x]=t;
    if(wc[x]){
        dfs2(wc[x],t);
        for(auto [v,w]:g[x]){
            if(v!=wc[x]&&v!=fa[x])dfs2(v,v);
        }
    }
}
int lca(int x,int y){
    while(top[x]!=top[y]){
        if(dep[top[x]]<=dep[top[y]])swap(x,y);
        x=fa[top[x]];
    }
    if(dep[x]<dep[y])return x;
    return y;
}
ll dis(int x,int y){
    return d[x]+d[y]-2*d[lca(x,y)];
}
pii st[N],st2[N];
int top1,top2;
void getct(int u,int f){
    siz[u]=1;
    maxp[u]=0;
    for(auto [v,w]:g[u]){
        if(v==f||vis[v])continue;
        getct(v,u);
        siz[u]+=siz[v];
        maxp[u]=max(maxp[u],siz[v]);
    }
    maxp[u]=max(maxp[u],tot-siz[u]);
    if(maxp[u]<maxp[rt])rt=u;
}
void getdis(int x,int f,ll ds){
    siz[x]=1;
    st[++top1]={x,ds};
    for(auto [v,w]:g[x]){
        if(vis[v]||v==f)continue;
        getdis(v,x,ds+w);
        siz[x]+=siz[v];
    }
}
void push(int u,int v){
    if(u>v)swap(u,v);
    p.push_back({u,v,dis(u,v)});
}
void solve(int x){
    top1=0;
    getdis(x,0,0);
    sort(st+1,st+1+top1);
    top2=0;
    for(int i=1;i<=top1;i++){
        int id=st[i].fst;
        ll d=st[i].scd;
        while(top2&&d<st2[top2].scd)top2--;
        if(top2)push(id,st2[top2].fst);
        st2[++top2]=st[i];
    }
    top2=0;
    for(int i=top1;i>=1;i--){
        int id=st[i].fst;
        ll d=st[i].scd;
        while(top2&&d<st2[top2].scd)top2--;
        if(top2)push(id,st2[top2].fst);
        st2[++top2]=st[i];
    }
}
void ctd(int x){
    vis[x]=1;
    solve(x);
    for(auto [v,w]:g[x]){
        if(vis[v])continue;
        tot=siz[v];
        maxp[rt=0]=N;
        getct(v,0);
        ctd(rt);
    }
}
int lowbit(int x){
    return x&(-x);
}
ll t[N],ans[5*N];
void add(int p,ll v){
    for(int i=p;i;i-=lowbit(i)){
        t[i]=min(t[i],v);
    }
}
ll query(int p){
    ll res=inf;
    for(int i=p;i<=n;i+=lowbit(i)){
        res=min(res,t[i]);
    }
    return res;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<n;i++){
        int u,v;
        ll w;
        cin>>u>>v>>w;
        g[u].push_back({v,w});
        g[v].push_back({u,w});
    }
    dfs(1,0);
    dfs2(1,1);
    maxp[rt=0]=N;
    tot=n;
    getct(1,0);
    ctd(rt);
    cin>>m;
    for(int i=1;i<=m;i++){
        int l,r;
        cin>>l>>r;
        if(l>=r)ans[i]=-1;
        else q.push_back({l,r,i});
    }
    for(int i=1;i<=n;i++)t[i]=inf;
    sort(p.begin(),p.end(),cmp);
    sort(q.begin(),q.end(),cmp);
    //cout<<p.size()<<endl;
    int cur=0;
    for(auto [l,r,id]:q){
        while(cur<p.size()&&p[cur].r<=r){
            add(p[cur].l,p[cur].id);
            cur++;
        }
        ans[id]=query(l);
    }
    for(int i=1;i<=m;i++){
        cout<<ans[i]<<endl;
    }
    return 0;
}
posted @ 2026-08-11 22:10  farenara_sayowell  阅读(0)  评论(0)    收藏  举报