Luogu P3899 湖南集训 更为厉害 题解 [ 紫 ] [ 可持久化线段树 ] [ dfs 序 ] [ 线段树合并 ]

更为厉害:可持久化做法有点意思,但线段树合并做法就很无脑了。

线段树合并做法

显然有三种 b 的位置的分类讨论。

ba 的祖先时

从祖先里选 b,从儿子里选 c,答案显然为 min(k,depa1)×(sizea1)

ba 没有祖先或孙子关系时

a,b 不可能同时是 c 的祖先,无解。

ab 的祖先时

答案显然为 sizex1,其中 xVdepa+1depxmin(depa+k,maxdep)

于是很容易想到以深度为下标,以每个节点作为根节点储存自己子树内的答案,每次查询的时候计算即可。

既然要把子树内的答案利用起来,显然要用线段树合并,时间复杂度 O(nlogn),注意线段树合并的时候合并要新开点。

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const int N=300005;
int n,q,dep[N],sz[N],mxd;
vector<int>g[N];
struct Node{
    int ls,rs;
    ll v;
};
struct Segtree{
    Node tr[40*N];
    int root[N],tot=0;
    void update(int &u,int ln,int rn,int x,ll k)
    {
        if(u==0)u=++tot;
        tr[u].v+=k;
        if(ln==rn)return;
        int mid=(ln+rn)>>1;
        if(x<=mid)update(lc(u),ln,mid,x,k);
        else update(rc(u),mid+1,rn,x,k);
    }
    int merge(int u,int v)
    {
        if(u==0||v==0)return u+v;
        int cur=++tot;
        tr[cur].v=tr[u].v+tr[v].v;
        tr[cur].ls=merge(tr[u].ls,tr[v].ls);
        tr[cur].rs=merge(tr[u].rs,tr[v].rs);
        return cur;
    }
    ll query(int u,int ln,int rn,int ql,int qr)
    {
        if(ql<=ln&&rn<=qr)return tr[u].v;
        int mid=(ln+rn)>>1;
        if(qr<=mid)return query(lc(u),ln,mid,ql,qr);
        if(ql>=mid+1)return query(rc(u),mid+1,rn,ql,qr);
        return query(lc(u),ln,mid,ql,qr)+query(rc(u),mid+1,rn,ql,qr);
    }
}tr1;
void dfs1(int u,int f)
{
    dep[u]=dep[f]+1;sz[u]=1;
    mxd=max(mxd,dep[u]);
    for(auto v:g[u])
    {
        if(v==f)continue;
        dfs1(v,u);
        sz[u]+=sz[v];
    }
}
void dfs2(int u,int f)
{
    tr1.update(tr1.root[u],1,mxd,dep[u],sz[u]-1);
    for(auto v:g[u])
    {
        if(v==f)continue;
        dfs2(v,u);
        tr1.root[u]=tr1.merge(tr1.root[u],tr1.root[v]);
    }
}
int main()
{
    //freopen("sample.in","r",stdin);
    //freopen("sample.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>q;
    for(int i=1;i<n;i++)
    {
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs1(1,0);
    dfs2(1,0);
    while(q--)
    {
        int p,k;
        cin>>p>>k;
        ll ans=1ll*min(k,dep[p]-1)*(sz[p]-1);
        if(dep[p]+1<=min(dep[p]+k,mxd))ans+=tr1.query(tr1.root[p],1,mxd,dep[p]+1,min(dep[p]+k,mxd));
        cout<<ans<<'\n';
    }
    return 0;
}

可持久化做法

首先分讨部分和上面相同,主要是最后一种情况的处理方式。

线段树合并做法是简单粗暴地对每个节点开一个线段树,而可持久化做法是利用了在子树内查询的性质,把子树转化为在 dfs 序的序列上求区间和

于是把所有点拍在 dfs 序上,预处理每个前缀的版本的线段树,然后查询的时候双指针作差即可。

时间复杂度 O(nlogn)

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const int N=300005;
int n,q,dep[N],dfn[N],cnt=0,sz[N],mxd;
vector<int>g[N];
void dfs1(int u,int f)
{
    dfn[u]=++cnt;dep[u]=dep[f]+1;sz[u]=1;
    mxd=max(mxd,dep[u]);
    for(auto v:g[u])
    {
        if(v==f)continue;
        dfs1(v,u);
        sz[u]+=sz[v];
    }
}
struct Node{
    int ls,rs;
    ll v;
};
struct Persegtree{
    Node tr[25*N];
    int root[N],tot=0;
    void update(int &u,int v,int ln,int rn,int x,ll k)
    {
        u=++tot;
        tr[u]=tr[v];
        tr[u].v+=k;
        if(ln==rn)return;
        int mid=(ln+rn)>>1;
        if(x<=mid)update(lc(u),lc(v),ln,mid,x,k);
        else update(rc(u),rc(v),mid+1,rn,x,k);
    }
    ll query(int u,int v,int ln,int rn,int ql,int qr)
    {
        if(qr<ln||ql>rn)return 0;
        if(ql<=ln&&rn<=qr)return tr[u].v-tr[v].v;
        int mid=(ln+rn)>>1;
        if(qr<=mid)return query(lc(u),lc(v),ln,mid,ql,qr);
        if(ql>=mid+1)return query(rc(u),rc(v),mid+1,rn,ql,qr);
        return query(lc(u),lc(v),ln,mid,ql,qr)+query(rc(u),rc(v),mid+1,rn,ql,qr);
    }
}tr1;
void dfs2(int u,int f)
{
    tr1.update(tr1.root[dfn[u]],tr1.root[dfn[u]-1],1,mxd,dep[u],sz[u]-1);
    for(auto v:g[u])
    {
        if(v==f)continue;
        dfs2(v,u);
    }
}
int main()
{
    //freopen("sample.in","r",stdin);
    //freopen("sample.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>q;
    for(int i=1;i<n;i++)
    {
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs1(1,0);
    dfs2(1,0);
    while(q--)
    {
        int p,k;
        cin>>p>>k;
        ll ans=1ll*min(dep[p]-1,k)*(sz[p]-1);
        ans+=tr1.query(tr1.root[dfn[p]+sz[p]-1],tr1.root[dfn[p]],1,mxd,dep[p]+1,min(mxd,dep[p]+k));
        cout<<ans<<'\n';
    }
    return 0;
}
posted @   KS_Fszha  阅读(1)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验
点击右上角即可分享
微信分享提示