Luogu P3899 湖南集训 更为厉害 题解 [ 紫 ] [ 可持久化线段树 ] [ dfs 序 ] [ 线段树合并 ]
更为厉害:可持久化做法有点意思,但线段树合并做法就很无脑了。
线段树合并做法
显然有三种 \(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 序 ] [ 线段树合并 ]的更多相关文章
- 主席树 || 可持久化线段树 || BZOJ 3653: 谈笑风生 || Luogu P3899 [湖南集训]谈笑风生
题面:P3899 [湖南集训]谈笑风生 题解: 我很喜欢这道题. 因为A是给定的,所以实质是求二元组的个数.我们以A(即给定的P)作为基点寻找答案,那么情况分两类.一种是B为A的父亲,另一种是A为B的 ...
- [Luogu P3899] [湖南集训]谈笑风生 (主席树)
题面 传送门:https://www.luogu.org/problemnew/show/P3899 Solution 你们搞的这道题啊,excited! 这题真的很有意思. 首先,我们可以先理解一下 ...
- luogu P3899 [湖南集训]谈笑风生 线段树合并
Code: #include<bits/stdc++.h> #define maxn 300002 #define ll long long using namespace std; vo ...
- luogu P3899 [湖南集训]谈笑风生
传送门 nmyzd,mgdhls,bnmbzdgdnlql,a,wgttxfs 对于一个点\(a\),点\(b\)只有可能是他的祖先或者在\(a\)子树里 如果点\(b\)是\(a\)祖先,那么答案为 ...
- 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 ...
- [NOIP10.6模拟赛]2.equation题解--DFS序+线段树
题目链接: 咕 闲扯: 终于在集训中敲出正解(虽然与正解不完全相同),开心QAQ 首先比较巧,这题是\(Ebola\)出的一场模拟赛的一道题的树上强化版,当时还口胡出了那题的题解 然而考场上只得了86 ...
- P3899 [湖南集训]谈笑风生
题目链接 https://www.lydsy.com/JudgeOnline/problem.php?id=3653 https://www.luogu.org/problemnew/show/P38 ...
- 洛谷P3899 [湖南集训]谈笑风生(线段树合并)
题意 题目链接 Sol 线段树合并板子题,目前我看到两种写法,分别是这样的. 前一种每次需要新建一个节点,空间是\(O(4nlogn)\) 后者不需要新建,空间是\(O(nlogn)\)(面向数据算空 ...
- Luogu 3899 [湖南集训]谈笑风生
BZOJ 3653权限题. 这题方法很多,但我会的不多…… 给定了$a$,我们考虑讨论$b$的位置: 1.$b$在$a$到根的链上,那么这样子$a$的子树中的每一个结点(除了$a$之外)都是可以成为$ ...
- P3899 [湖南集训]谈笑风生 主席树
#include<iostream> #include<string.h> #include<algorithm> #include<stdio.h> ...
随机推荐
- Abp Vnext Vue Element UI实现
Abp Vnext Vue Element UI实现版本开箱即用的中后台前端/设计解决方案 链接 AbpPro Vben5 Vue Element Plus 预览 点击查看效果 文档地址 点击查看文档 ...
- canvas(五)绘制文本
1.绘制描边文本 说明:描边的属性是共用的,无论是绘制直线还是文字,所以有需要的话要单独设置描边颜色,相关语法如下 语法 说明 ctx.strokeStyle 设置描边的颜色(文本颜色) ctx.fo ...
- controller返回路径问题
项目打包后,报错template might not exist or might not be accessible by any of the configured Template Resolv ...
- axmapcontrol的一点发现
最近在做一个功能,从主窗体弹出子窗体(包含地图控件)显示地图.不过初始化的过程比较耗时(主要是连接远程数据),所以想改变鼠标样式为等待.本来是比较简单的思路 A:主窗体,B:子窗体 a.cursor= ...
- CVE-2023-48409 Mali GPU 整数溢出导致堆越界写
CVE-2023-48409 Mali GPU 整数溢出导致堆越界写 https://github.com/0x36/Pixel_GPU_Exploit 漏洞原语:假设分配的大小为 0x3004, ...
- HttpClientFactory in ASP.NET Core 2.1 Part 5: 日志
HttpClientFactory in ASP.NET Core 2.1 Part 5: 日志 原文地址:https://www.stevejgordon.co.uk/httpclientfacto ...
- 中电金信召开“源启 AI+”人工智能应用场景与发展培训暨业务研讨会
近年来,国务院国资委把加快发展人工智能放在国资央企全局工作中统筹谋划,作为产业焕新行动和启航行动部署的主要方向,制定印发行动计划,组织召开中央企业人工智能专题推进会,开展"AI+" ...
- Spring Boot + K8S 中的滚动发布、优雅停机、弹性伸缩、应用监控、配置分离
前言 K8s + SpringBoot实现零宕机发布:健康检查+滚动更新+优雅停机+弹性伸缩+Prometheus监控+配置分离(镜像复用) 配置 健康检查 健康检查类型:就绪探针(readiness ...
- Qt/C++音视频开发69-保存监控pcm音频数据到mp4文件/监控录像/录像存储和回放/264/265/aac/pcm等
一.前言 用ffmpeg做音视频保存到mp4文件,都会遇到一个问题,尤其是在视频监控行业,就是监控摄像头设置的音频是PCM/G711A/G711U,解码后对应的格式是pcm_s16be/pcm_ala ...
- Qt/C++中英输入法/嵌入式输入法/小数字面板/简繁切换/特殊字符/支持Qt456
一.前言 在嵌入式板子上由于没有系统层面的输入法支持,所以都绕不开一个问题,那就是在需要输入的UI软件中,必须提供一个输入法来进行输入,大概从Qt5.7开始官方提供了输入法的源码,作为插件的形式加入到 ...