[Ynoi2004] Range Pair Minimum Tree Distance Query

题面

题目描述

给定一棵有边权的无根树,定义 \(\operatorname{dist}(i,j)\) 代表树上点 \(i\) 和点 \(j\) 之间的距离。

有一些询问,对于每一组询问,给出 \(l,r\),求 \(\displaystyle\min_{l\leq i \lt j \leq r} \operatorname{dist}(i,j)\)

数据范围

  • \(1\leq n\leq2\times 10^5\)
  • \(1\leq q\leq 10^6\)
  • 边权 \(\leq 10^9\)

题解

询问看起来是二维前缀 min 的形式,然而可以产生贡献的点有多达 \(O(n^2)\) 个。

考虑是否可以删去一些没用的点,即我们希望尽可能的删去 \(\exists i\leq i'\lt j' \leq j,\operatorname{dist}(i',j')<\operatorname{dist}(i,j)\)\((i,j)\)

考虑点分治,令分治中心为 \(root\),计算 \(d_u=\operatorname{dist}(root,u)\),对于每个点 \(u\),我们每次仅仅保留 \((l_u,u)\)\((u,r_u)\),其中 \(l_u=\max\{ v|v\lt u \wedge d_v\leq d_u\}\)\(r_u=\min\{ v|v\gt u \wedge d_v\leq d_u\}\)。这样总点数减少到了 \(O(n\log n)\)

为什么这样是对的

\(l_u\) 举例,采用反证法。

  • 假设存在 \(v\) 使得 \(l_u \lt v\lt u\)\((v,u)\) 的贡献漏了。由于 \((v,u)\) 会产生贡献且 \(l_u\neq v\),说明 \(\displaystyle d_u\lt d_v\lt \min_{p=u+1}^{v-1} d_p\),根据 \(r\) 的定义可知 \(r_v\geq u\)。因为 \((v,u)\) 没有被算到,所以 \(r_v\neq u\),即 \(r_v>u\),说明 \(d_v>d_u\),与 \(l_u \lt v\lt u\) 矛盾。

  • 假设存在 \(v\) 使得 \(\operatorname{dist}(v,u) \lt \operatorname{dist}(l_u,u)\)\((v,u)\) 的贡献漏了。根据 \(l_u\) 的定义可知,有 \(v \lt l_u\lt u\)。由于 \((v,u)\) 会产生贡献且 \(l_u\neq v\),说明 \(\displaystyle d_u,d_v\lt \min_{p=u+1}^{v-1} d_p\),而这与 \(d_{l_u}\leq d_u\) 相悖。

所以这么选点是对的。

然后就是离线扫描线二维前缀 min,时间复杂度 \(O(n\log^2 n+q\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=2e5+9;
const int Q=1e6+9;
const int lgN=2e1;
const int NlgN=2e6+9;
const ll inf=1e18+7;

int fi[N],ne[N<<1],to[N<<1],w[N<<1],adj;
inline void AddEdge(int x,int y,int z){
    ne[++adj]=fi[x];
    fi[x]=adj;
    to[adj]=y;
    w[adj]=z;
}

int fa[N],elr[N<<1],pos[N],ecnt;
ll dep[N];
inline void DFS(int x){
    elr[++ecnt]=x;
    pos[x]=ecnt;
    for(int i=fi[x];i;i=ne[i]){
        int y=to[i];
        if(y==fa[x]) continue ;
        dep[y]=dep[x]+w[i];
        fa[y]=x;
        DFS(y);
        elr[++ecnt]=x;
    }
}
int mn[N<<1][lgN],lg[N<<1];
inline void Init(){
    for(int i=2;i<=ecnt;i++) lg[i]=lg[i>>1]+1;
    for(int i=1;i<=ecnt;i++) mn[i][0]=pos[elr[i]];
    for(int k=1;k<=lg[ecnt];k++){
        for(int i=1;i<=ecnt-(1<<k)+1;i++) mn[i][k]=min(mn[i][k-1],mn[i+(1<<k-1)][k-1]);
    }
}
inline int LCA(int u,int v){
    u=pos[u],v=pos[v];
    if(u>v) swap(u,v);
    int k=lg[v-u+1];
    return elr[min(mn[u][k],mn[v-(1<<k)+1][k])];
}
inline ll Dist(int u,int v){return dep[u]+dep[v]-2*dep[LCA(u,v)];}

int vis[N],siz[N];
inline void GetGrv(int x,int f,int tot,int &grv){
    bool flag=1;
    siz[x]=1;
    for(int i=fi[x];i;i=ne[i]){
        int y=to[i];
        if(vis[y]) continue ;
        if(y==f) continue ;
        GetGrv(y,x,tot,grv);
        siz[x]+=siz[y];
        if(siz[y]>tot/2) flag=0;
    }
    if(tot-siz[x]>tot/2) flag=0;
    if(flag) grv=x;
}
inline void GetMem(int x,int f,vector<int> &v){
    siz[x]=1;
    v.push_back(x);
    for(int i=fi[x];i;i=ne[i]){
        int y=to[i];
        if(vis[y]) continue ;
        if(y==f) continue ;
        GetMem(y,x,v);
        siz[x]+=siz[y];
    }
}
int pl[NlgN<<1],pr[NlgN<<1],po[NlgN<<1],pcnt;
inline void Divide(int x,int tot){
    GetGrv(x,0,tot,x);
    vis[x]=1;

    vector<int> v,l(tot,0),r(tot,0);
    vector<ll> d(tot);
    GetMem(x,0,v);
    sort(v.begin(),v.end());
    for(int i=0;i<tot;i++) d[i]=Dist(x,v[i]);
    vector<int> stk;
    for(int i=0;i<tot;i++){
        while(stk.size()&&d[i]<d[stk.back()]) stk.pop_back();
        if(stk.size()) l[i]=v[stk.back()];
        stk.push_back(i);
    }
    stk.clear();
    for(int i=tot-1;~i;i--){
        while(stk.size()&&d[i]<d[stk.back()]) stk.pop_back();
        if(stk.size()) r[i]=v[stk.back()];
        stk.push_back(i);
    }
    for(int i=0;i<tot;i++){
        if(l[i]) pcnt++,pl[pcnt]=l[i],pr[pcnt]=v[i];
        if(r[i]) pcnt++,pl[pcnt]=v[i],pr[pcnt]=r[i];
    }

    for(int i=fi[x];i;i=ne[i]){
        int y=to[i];
        if(vis[y]) continue ;
        Divide(y,siz[y]);
    }
}

int n;
ll tr[N];
inline void Modify(int x,ll k){
    while(x<=n){
        tr[x]=min(tr[x],k);
        x+=x&-x;
    }
}
inline ll Query(int x){
    ll res=inf;
    while(x){
        res=min(res,tr[x]);
        x&=x-1;
    }
    return res;
}

int ql[Q],qr[Q],qo[Q],q;
ll ans[Q];

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);

    cin>>n;
    for(int i=1,u,v,w;i<n;i++) cin>>u>>v>>w,AddEdge(u,v,w),AddEdge(v,u,w);
    cin>>q;
    for(int i=1;i<=q;i++) cin>>ql[i]>>qr[i];
    
    DFS(1),Init();
    Divide(1,n);
    for(int i=1;i<=pcnt;i++) po[i]=i;
    sort(po+1,po+pcnt+1,[](int i,int j){return pr[i]<pr[j];});
    for(int i=1;i<=q;i++) qo[i]=i;
    sort(qo+1,qo+q+1,[](int i,int j){return qr[i]<qr[j];});

    for(int i=1;i<=n;i++) tr[i]=inf;
    for(int i=1,j=1,k=1;i<=n;i++){
        while(j<=pcnt&&pr[po[j]]<=i) Modify(n-pl[po[j]]+1,Dist(pl[po[j]],pr[po[j]])),j++;
        while(k<=q&&qr[qo[k]]<=i) ans[qo[k]]=Query(n-ql[qo[k]]+1),k++;
    }

    for(int i=1;i<=q;i++) cout<<(ans[i]!=inf?ans[i]:-1)<<endl;

    return 0;
}
posted @ 2025-04-15 16:35  JoeyJiang  阅读(22)  评论(0)    收藏  举报