Luogu P3899 湖南集训 更为厉害 题解 [ 紫 ] [ 可持久化线段树 ] [ dfs 序 ] [ 线段树合并 ]
更为厉害:可持久化做法有点意思,但线段树合并做法就很无脑了。
线段树合并做法
显然有三种
当 为 的祖先时
从祖先里选
当 与 没有祖先或孙子关系时
当 为 的祖先时
答案显然为
于是很容易想到以深度为下标,以每个节点作为根节点储存自己子树内的答案,每次查询的时候计算即可。
既然要把子树内的答案利用起来,显然要用线段树合并,时间复杂度
#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 序上,预处理每个前缀的版本的线段树,然后查询的时候双指针作差即可。
时间复杂度
#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;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验