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> ...
随机推荐
- Dockerfile&Docker-Compose之基础
使用了很久的docker,之前却从来没有总结过, 于是开此篇来记录平常使用Dockerfile和docker-compose.yaml的点滴, 先从基础命令开始哦 [Dockerfile] Docke ...
- Windows系统下PhpStorm+Xdebug安装与调试
环境说明: 系统:Windows10 PhpStorm:2019.3.2 PHP版本:7.3.21 Xdebug版本 :2.7.2 一.Xdebug介绍 官网地址:https://xdebug.org ...
- 鸿蒙NEXT元服务:收藏、卡片、用户协议、隐私声明、分享链接、评分与评论
相比应用,元服务的功能藏的比较深,这里记录一下常用功能的位置. 1.收藏(添加至我的服务) 打开元服务-->右上角四个点-->添加至我的服务-->手机滑到负一屏-->点击&qu ...
- Python 学习记录(2)
NumPy数组规整 对数组进行各种变化 转换数组形状 import numpy as np a=np.arange(1,6+1) b=np.reshape(a,(2,3)) print(a) prin ...
- T语言开发笔记1
为什么会有开发语言的想法 在2012年,我准备开发一个给前端切图使用的屏幕取色器. 需求很简单,前端经常需要获取设计稿特定位置的颜色代码.虽然当时 PhotoShop 提供了内部取色器,但操作麻烦,而 ...
- Could not retrieve transation read-only status server 的解决办法
问题描述: 在项目开发的过程中,使用Hibernate的ORM进行建表时,出现 " Could not retrieve transation read-only status server ...
- Java网约车项目实战:实现抢单功能详解
在网约车项目中,抢单功能是非常关键的一部分,它决定了司机能否及时响应乘客的订单,提高整个平台的运营效率.本文将详细介绍如何使用Java来实现网约车项目的抢单功能,并提供一个完整的代码示例,以便读者能够 ...
- vue引入element-ui插件 “export ‘default‘ (imported as ‘Vue‘) was not found in ‘vue‘
注意:出现该问题的原因主要是使用的Vue版本与Element-UI的版本不匹配. Vue.Vue-cli与Element-UI之间版本的正确的匹配关系是: Vue库版本 Vue-cli库版本 Elem ...
- 【Windows】修改虚拟内存位置
问题:系统优化中,希望将pagefile.sys文件(即虚拟内存)移动到其他盘中,在网上查找解决办法,找了很多,按照方法设置完成后,pagefile.sys文件依然存在,后来,找到了一篇文章解决了,现 ...
- JVM中线程的状态以及状态间的转换
线程在一定条件下,状态会发生变化.线程一共有以下几种状态: 新建状态(New):新创建了一个线程对象. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的 ...