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> ...
随机推荐
- Linux之SaltStack
SaltStack是基于Python开发的一套C/S架构配置管理工具(功能不仅仅是配置管理,如使用salt-cloud配置AWS EC2实例),它的底层使用ZeroMQ消息队列pub/sub方式通信, ...
- 什么是.NET的强类型字符串(Strongly typed string)?
在.NET中,强类型字符串(Strongly typed string)并不是一个官方的概念,是指使用特定的结构来表示某种类型字符串数据的编码实践.类似于枚举,可以提供编译时检查类型,减少运行时错误, ...
- OpenEuler文件被锁定的解决方法|网卡修改不生效的解决办法
欧拉系统(含centos等linux系统)修改文件,一直提示readonly,不让改.原因有可能是这个文件给锁定了. 解决方法: 使用以下两个命令: • chattr 改变文件属性 • lsattr ...
- JAVA开发规范v1.0
01-中铜国贸JAVA开发规范v1.0 一.编程规约 (一)命名风格 [强制]代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束. 反例:_name / _name / $Obje ...
- sqlserver查询某数据库下表的占用空间
要查看 SQL Server 中哪个表占用的空间最多,您可以使用以下查询来列出所有表及其占用的空间大小,并按照占用空间从大到小进行排序: SELECT t.NAME AS TableName, p.r ...
- 在 Ubuntu GUI 中以 root 身份登录
参考:https://zhuanlan.zhihu.com/p/610049537?utm_id=0 有一些桌面用户想以 root 身份登录.这不是什么明智之举,但肯定是可以做到的. 默认情况下,Ub ...
- k8s pod重启 deployment重启
1.15版本之后可通过kubectl rollout restart deployment -n 命令来实现滚动重启POD 该命令会先创建待用POD,待新POD运行成功后,再关闭原有POD.因此需要保 ...
- SpringBoot配置文件敏感信息加密,springboot配置文件数据库密码加密jasypt
使用过SpringBoot配置文件的朋友都知道,资源文件中的内容通常情况下是明文显示,安全性就比较低一些.打开application.properties或application.yml,比如mysq ...
- Web浏览器播放rtsp视频流详细解决方案
1.背景 在当前项目中,需要实现Web端直接播放RTSP视频流.该功能的核心目标是使得用户能够通过浏览器观看来自不同品牌的IPC(Internet Protocol Camera)设备的实时视频流.主 ...
- rysnc使用手册
rsync 是一个用于在本地和远程计算机之间同步文件和目录的命令行工具.它具有许多强大的功能,包括增量传输.压缩和保留权限等.以下是一些 rsync 的常用选项和用法示例: 基本用法 rsync [O ...