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

线段树合并做法

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

当 \(b\) 为 \(a\) 的祖先时

从祖先里选 \(b\),从儿子里选 \(c\),答案显然为 \(\min(k,dep_a-1)\times (size_a-1)\)。

当 \(b\) 与 \(a\) 没有祖先或孙子关系时

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

当 \(a\) 为 \(b\) 的祖先时

答案显然为 \(\sum size_x-1\),其中 \(x \in V_{dep_a+1 \le dep_x \le \min(dep_a+k,maxdep)}\)。

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

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

#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(n\log n)\)。

#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;
}

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

  1. 主席树 || 可持久化线段树 || BZOJ 3653: 谈笑风生 || Luogu P3899 [湖南集训]谈笑风生

    题面:P3899 [湖南集训]谈笑风生 题解: 我很喜欢这道题. 因为A是给定的,所以实质是求二元组的个数.我们以A(即给定的P)作为基点寻找答案,那么情况分两类.一种是B为A的父亲,另一种是A为B的 ...

  2. [Luogu P3899] [湖南集训]谈笑风生 (主席树)

    题面 传送门:https://www.luogu.org/problemnew/show/P3899 Solution 你们搞的这道题啊,excited! 这题真的很有意思. 首先,我们可以先理解一下 ...

  3. luogu P3899 [湖南集训]谈笑风生 线段树合并

    Code: #include<bits/stdc++.h> #define maxn 300002 #define ll long long using namespace std; vo ...

  4. luogu P3899 [湖南集训]谈笑风生

    传送门 nmyzd,mgdhls,bnmbzdgdnlql,a,wgttxfs 对于一个点\(a\),点\(b\)只有可能是他的祖先或者在\(a\)子树里 如果点\(b\)是\(a\)祖先,那么答案为 ...

  5. HDU-3974 Assign the task题解报告【dfs序+线段树】

    There is a company that has N employees(numbered from 1 to N),every employee in the company has a im ...

  6. [NOIP10.6模拟赛]2.equation题解--DFS序+线段树

    题目链接: 咕 闲扯: 终于在集训中敲出正解(虽然与正解不完全相同),开心QAQ 首先比较巧,这题是\(Ebola\)出的一场模拟赛的一道题的树上强化版,当时还口胡出了那题的题解 然而考场上只得了86 ...

  7. P3899 [湖南集训]谈笑风生

    题目链接 https://www.lydsy.com/JudgeOnline/problem.php?id=3653 https://www.luogu.org/problemnew/show/P38 ...

  8. 洛谷P3899 [湖南集训]谈笑风生(线段树合并)

    题意 题目链接 Sol 线段树合并板子题,目前我看到两种写法,分别是这样的. 前一种每次需要新建一个节点,空间是\(O(4nlogn)\) 后者不需要新建,空间是\(O(nlogn)\)(面向数据算空 ...

  9. Luogu 3899 [湖南集训]谈笑风生

    BZOJ 3653权限题. 这题方法很多,但我会的不多…… 给定了$a$,我们考虑讨论$b$的位置: 1.$b$在$a$到根的链上,那么这样子$a$的子树中的每一个结点(除了$a$之外)都是可以成为$ ...

  10. P3899 [湖南集训]谈笑风生 主席树

    #include<iostream> #include<string.h> #include<algorithm> #include<stdio.h> ...

随机推荐

  1. golang之gRPC

    相关链接: grpc: https://grpc.io/docs/languages/go/quickstart/ protobuf: https://protobuf.dev/programming ...

  2. pycharm之常用插件

    参考:http://pycharm.iswbm.com/zh_CN/latest/ 1. Key Promoter X 如果让我给新手推荐一个 PyCharm 必装插件,那一定是 Key Promot ...

  3. Winform TabControl动态添加TabPage

    在Winform中,标签页是我们很难绕开的一个控件,而且,我们经常有动态添加标签页的需求. 这里介绍一个最简单的添加方法: 首先,我们把需要添加的内容做成UserControl,这样,我们就可以在添加 ...

  4. 腾讯云 TStor 私有云存储获统信+海光/兆芯官方认证

    腾讯云 TStor 是一款分布式存储产品,致力于解决私有云.混合云下的各类存储需求.产品紧跟国内信创生态,持续加强自主可控能力.目前,TStor 已经支持国内主流国产操作系统和硬件,如中标麒麟操作系统 ...

  5. Flutter TickerProvider使用

    Flutter TickerProvider使用 当需要使用Animation controller时,需要在控制器初始化时传递一个vsync参数,此时需要用到TickerProvider,例如 cl ...

  6. 【Javaweb】基础开发流程与介绍

    本文档写于2022年7月29日,由于个人水平有限,可能存在一些问题,因此仅供参考 @萌狼蓝天 JavaWeb基础开发流程 1.确定系统和功能 在此以"宠物管理系统"为例,要开发一个 ...

  7. 【软件】【逆向】StarUML regist

    // 安装npm brew install node //安装打包工具 npm install asar -g C:\Program Files\StarUML\resources StarUML\r ...

  8. 【Linux】当初的学习笔记

    目录 Linux 笔记 linux基本概念 linux终端四部分 linux的实质 linux系统操作命令 查询用户 who who -H whoami 修改密码 快速切换到用户目录 sshd sys ...

  9. Llama 3.2 900亿参数视觉多模态大模型本地部署及案例展示

    Llama 3.2 900亿参数视觉多模态大模型本地部署及案例展示 本文将介绍如何在本地部署Llama 3.2 90B(900亿参数)视觉多模态大模型,并开发一些Use Case,展示其强大的视觉理解 ...

  10. Redis反序列化LocalDateTime时报错

    今天在整合redis和spring boot的时候,遇到了一个错误,记录一下. 报错如下: Could not read JSON: Cannot construct instance of `jav ...