luogu题解 P4092 【[HEOI2016/TJOI2016]树】树链剖分
题目链接:
瞎扯--\(O(Q \log^3 N)\)解法
这道先yy出了一个\(O(Q \log^3 N)\),的做法,先树链剖分。
对于加标记操作,找到那个点所在的链,将其\(top\)标记一下,然后该点到根节点区间和+1.
对于查询操作,先看这个点所在链有没有标记,如果没有,就一直向上跳直到找到一条标记了的链,然后在那条链上根据到根节点区间和进行倍增/二分
然后出去吃饭的时候忽然想到了\(O(Q \log^2 N)\)的解法,于是刚刚这个解法刚打完还没有查错,放在这做一个参考
代码:
include
include
include
include
include
include
include
define ll long long
define ri register int
using namespace std;
const int maxn=100005;
const int inf=0x7fffffff;
template inline void read(T &x){
x=0;int ne=0;char c;
while(!isdigit(c=getchar()))ne=c'-';
x=c-48;
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
x=ne?-x:x;
return ;
}
int n,q;
struct Edge{
int ne,to;
}edge[maxn<<1];
int h[maxn],num_edge=0;
inline void add_edge(int f,int t){
edge[++num_edge].ne=h[f];
edge[num_edge].to=t;
h[f]=num_edge;
return ;
}
int dep[maxn],fa[maxn],size[maxn],son[maxn],top[maxn],dfn[maxn],rnk[maxn],cnt=0;
void dfs_1(int now){
int v;size[now]=1;
for(ri i=h[now];i;i=edge[i].ne){
v=edge[i].to;
if(vfa[now])continue;
fa[v]=now,dep[v]=dep[now]+1;
dfs_1(v);
size[now]+=size[v];
if(!son[now]||size[son[now]]<size[v])son[now]=v;
}
return ;
}
void dfs_2(int now,int t){
int v;top[now]=t;
dfn[now]=++cnt,rnk[cnt]=now;
if(!son[now])return ;
dfs_2(son[now],t);
for(ri i=h[now];i;i=edge[i].ne){
v=edge[i].to;
if(vfa[now]||vson[now])continue;
dfs_2(v,v);
}
return ;
}
int sum[maxn<<2],tag[maxn<<2],L,R,dta,ok[maxn];
void build(int now,int l,int r){
if(lr){
sum[now]=ok[rnk[l]];
return ;
}
int mid=(l+r)>>1;
build(now<<1,l,mid);
build(now<<1|1,mid+1,r);
return ;
}
void pushdown(int now,int ln,int rn){
if(tag[now]){
sum[now<<1]+=tag[now]ln;
sum[now<<1|1]+=tag[now]rn;
tag[now<<1]+=tag[now];
tag[now<<1|1]+=tag[now];
tag[now]=0;
}
return ;
}
void update(int now,int l,int r){
if(L<=l&&r<=R){
sum[now]+=dta*(r-l+1);
tag[now]+=dta;
return ;
}
int mid=(l+r)>>1;
pushdown(now,mid-l+1,r-mid);
if(L<=mid)update(now<<1,l,mid);
if(mid<R)update(now<<1|1,mid+1,r);
sum[now]=sum[now<<1]+sum[now<<1|1];
return ;
}
int query(int now,int l,int r){
if(L<=l&&r<=R){
return sum[now];
}
int mid=(l+r)>>1,ans=0;
pushdown(now,mid-l+1,r-mid);
if(L<=mid)ans+=query(now<<1,l,mid);
if(mid<R)ans+=query(now<<1|1,mid+1,r);
sum[now]=sum[now<<1]+sum[now<<1|1];
return ans;
}
void update_path(int x,int y){
dta=1;ok[top[x]]=1;//该条链上有一个标记的点
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
L=dfn[top[x]],R=dfn[x];
update(1,1,n);
}
if(dfn[x]<dfn[y])swap(x,y);
L=dfn[x],R=dfn[y];
update(1,1,n);
return ;
}
inline int solve(int x,int y){
int tmp,val,p=0,k=1,len,ans=0;
bool flag=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
len=dfn[x]-dfn[top[x]];
if(ok[top[x]]){
L=dfn[top[x]],R=dfn[x],
tmp=query(1,1,n);
p=0,k=1,flag=0;
while(k!=0){
L=dfn[x+p+k],R=dfn[x];
if(query(1,1,n)>tmp)flag=1,k=k>>1;
else p=p+k,k=k<<1;
while(p+k>len)k=k>>1;
}
if(flag)return ans+dfn[x+p]-dfn[x];
}
ans+=len;
x=fa[top[x]];
}
if(dfn[x]>dfn[y])swap(x,y);
L=dfn[x],R=dfn[y],len=dfn[y]-dfn[x];
tmp=query(1,1,n);
p=0,k=1;
//cout<<y<<endl;
if(xy)return ans;
while(k!=0){
L=dfn[x+p+k],R=dfn[x];
if(query(1,1,n)>tmp)k=k>>1;
else p=p+k,k=k<<1;
//if(y3)cout<<k<<' '<<p<<endl;
while(p+k>len)k=k>>1;
}
return ans+dfn[x+p]-dfn[x];
}
int main(){
char opt[5];
int x,y,z;
read(n),read(q);
for(ri i=1;i<n;i++){
read(x),read(y);
add_edge(x,y);
add_edge(y,x);
}
dep[1]=1,fa[1]=0;
dfs_1(1);
dfs_2(1,1);
ok[dfn[1]]=1;
build(1,1,n);
while(q--){
scanf("%s",opt);
if(opt[0]'C'){
read(x);
//cout<<x<<"-----"<<endl;
update_path(1,x);
}
else{
read(x);
//cout<<x<<"***"<<endl;
printf("%d\n",solve(x,1));
}
}
return 0;
}
- 分析---$O(Q \log^2 N)$解法
首先我想到了一个错误的解法,就是因为链是线段树上一个连续的区间,每个$[dfn[x],dfn[top[x]]]$线段树区间有个$mx$值,表示,$x$到$top[x]$路径中距离它最近标记的祖先,加标记时比较原有标记深度与新标记深度然后更新。查询的时候查询$x$到$top[x]$的区间最大之就可以了,如果没有,就一直往上跳直至找到
然而这个解法有个错误我SB地没有发现,就是你更新区间最大值时,$x$上的祖先节点也会被更新到(因为深度更小),再次感谢wjyyy和creed_两位大佬指出我的错误
正解应该是更新子树,将子树的最大值更新,查询照样,相比于我错误的代码只需改一句话
代码:
include
include
include
include
include
include
include
define ll long long
define ri register int
using namespace std;
const int maxn=100005;
const int inf=0x7fffffff;
template inline void read(T &x){
x=0;int ne=0;char c;
while(!isdigit(c=getchar()))ne=c'-';
x=c-48;
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
x=ne?-x:x;
return ;
}
int n,q;
struct Edge{
int ne,to;
}edge[maxn<<1];
int h[maxn],num_edge=0;
inline void add_edge(int f,int t){
edge[++num_edge].ne=h[f];
edge[num_edge].to=t;
h[f]=num_edge;
return ;
}
int dep[maxn],fa[maxn],size[maxn],son[maxn],top[maxn],dfn[maxn],rnk[maxn],cnt=0;
void dfs_1(int now){
int v;size[now]=0;
for(ri i=h[now];i;i=edge[i].ne){
v=edge[i].to;
if(vfa[now])continue;
fa[v]=now,dep[v]=dep[now]+1;
dfs_1(v);
size[now]+=size[v];
if(!son[now]||size[son[now]]<size[v])son[now]=v;
}
return ;
}
void dfs_2(int now,int t){
int v;top[now]=t;
dfn[now]=++cnt,rnk[cnt]=now;
if(!son[now])return ;
dfs_2(son[now],t);
for(ri i=h[now];i;i=edge[i].ne){
v=edge[i].to;
if(vfa[now]||vson[now])continue;
dfs_2(v,v);
}
return ;
}
int mx[maxn<<2],L,R,dta;
void build(int now,int l,int r){
if(lr){
if(rnk[l]1)mx[now]=1;
else mx[now]=0;
return ;
}
int mid=(l+r)>>1;
build(now<<1,l,mid);
build(now<<1|1,mid+1,r);
if(dep[mx[now<<1]]>dep[mx[now<<1|1]]){
mx[now]=mx[now<<1];
}
else mx[now]=mx[now<<1|1];
return ;
}
void update(int now,int l,int r){
if(L<=l&&r<=R){
if(dep[mx[now]]<dep[dta]){
mx[now]=dta;
}
return ;
}
int mid=(l+r)>>1;
if(L<=mid)update(now<<1,l,mid);
if(mid<R)update(now<<1|1,mid+1,r);
if(dep[mx[now<<1]]>dep[mx[now<<1|1]]){
mx[now]=mx[now<<1];
}
else mx[now]=mx[now<<1|1];
return ;
}
int query(int now,int l,int r){
if(L<=l&&r<=R){
return mx[now];
}
int mid=(l+r)>>1,ans=0,tmp;
if(L<=mid){
int tmp=query(now<<1,l,mid);
if(dep[ans]<dep[tmp])ans=tmp;
}
if(mid<R){
int tmp=query(now<<1|1,mid+1,r);
if(dep[ans]<dep[tmp])ans=tmp;
}
return ans;
}
void update_path(int x){
dta=x;
//L=R=dfn[x];
L=dfn[x],R=dfn[x]+size[x];
update(1,1,n);
return ;
}
int query_path(int x){
int ans=0;
while(top[x]!=1){
L=dfn[top[x]],R=dfn[x];
ans=query(1,1,n);
if(ans!=0)return ans;
x=fa[top[x]];
}
L=dfn[1],R=dfn[x];
ans=query(1,1,n);
return ans;
}
int main(){
char opt[5];
int x,y,z;
read(n),read(q);
for(ri i=1;i<n;i++){
read(x),read(y);
add_edge(x,y);
add_edge(y,x);
}
dep[0]=-1,dep[1]=1,fa[1]=0;
dfs_1(1);
dfs_2(1,1);
build(1,1,n);
while(q--){
//cout<<q<<endl;
scanf("%s",opt);
if(opt[0]=='C'){
read(x);
update_path(x);
}
else{
read(x);
printf("%d\n",query_path(x));
}
}
return 0;
}
```
luogu题解 P4092 【[HEOI2016/TJOI2016]树】树链剖分的更多相关文章
- 洛谷P4092 [HEOI2016/TJOI2016]树 并查集/树链剖分+线段树
正解:并查集/树链剖分+线段树 解题报告: 传送门 感觉并查集的那个方法挺妙的,,,刚好又要复习下树剖了,所以就写个题解好了QwQ 首先说下并查集的方法趴QwQ 首先离线,读入所有操作,然后dfs遍历 ...
- 洛谷 P4092 [HEOI2016/TJOI2016]树 || bzoj4551
https://www.lydsy.com/JudgeOnline/problem.php?id=4551 https://www.luogu.org/problemnew/show/P4092 这当 ...
- 线段树&数链剖分
傻逼线段树,傻逼数剖 线段树 定义: 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点. 使用线段树可以快速的查找某一个节点在若干条线段中出现 ...
- [洛谷P4092][HEOI2016/TJOI2016]树
题目大意:给你一棵树,有两个操作: $C\;x:$给第$x$个节点打上标记 $Q\;x:$询问第$x$个节点的祖先中最近的打过标记的点(自己也是自己的祖先) 题解:树剖,可以维护区间或,然后若一段区间 ...
- P4092 [HEOI2016/TJOI2016]树
题目描述 在2016年,佳媛姐姐刚刚学习了树,非常开心.现在他想解决这样一个问题:给定一颗有根树(根为1),有以下两种操作: 标记操作:对某个结点打上标记(在最开始,只有结点1有标记,其他结点均无标记 ...
- 题解 【HEOI2016】tree树
题面 解析 其实这题可以考虑离线做法,用并查集解决. 因为仔细想,添加标记并不方便, 但如果用并查集记录下祖先, 再一一删除,就会方便很多. 先把每次操作记录下来, 同时记录下每个点被标记的次数(因为 ...
- [题解] LuoguP4091 [HEOI2016/TJOI2016]求和
传送门 首先我们来看一下怎么求\(S(m,n)\). 注意到第二类斯特林数的组合意义就是将\(m\)个不同的物品放到\(n\)个没有区别的盒子里,不允许有空盒子的方案数. 那么将\(m\)个不同的物品 ...
- 树链剖分好(du)题(liu)选做
1.luogu P4315 月下"毛景树" 题目链接 前言: 这大概是本蒟蒻A掉的题里面码量最大的一道题了.我自认为码风比较紧凑,但还是写了175行. 从下午2点多调到晚上8点.中 ...
- UOJ#30/Codeforces 487E Tourists 点双连通分量,Tarjan,圆方树,树链剖分,线段树
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ30.html 题目传送门 - UOJ#30 题意 uoj写的很简洁.清晰,这里就不抄一遍了. 题解 首先建 ...
随机推荐
- 在testrpc以太坊测试环境部署智能合约
2018年03月13日 09:20:54 思无邪-machengyu 阅读数 2683 版权声明:本文为博主原创文章,转载请务必注明出处,否则追究法律责任 https://blog.csdn.ne ...
- Build Telemetry for Distributed Services之Elastic APM
官网地址:https://www.elastic.co/guide/en/apm/get-started/current/index.html Overview Elastic APM is an a ...
- 分布式存储——Build up a High Availability Distributed Key-Value Store
原文链接 Preface There are many awesome and powerful distributed NoSQL in the world, like Couchbase, Mon ...
- JAVA 基础编程练习题41 【程序 41 猴子分桃】
41 [程序 41 猴子分桃] 题目:海滩上有一堆桃子,五只猴子来分.第一只猴子把这堆桃子凭据分为五份,多了一个,这只猴子把 多的一个扔入海中,拿走了一份.第二只猴子把剩下的桃子又平均分成五份,又多了 ...
- 推荐一个加载动图的网站loading.io
推荐一个非常好玩的loading gif的资源网站:https://loading.io/ 里面有各种loading的动图.
- python进阶----logging模块
在工作中经常要打印一些日志,下面介绍一下python中的logging模块 首先,先了解一下日志的级别,主要分为以下5种: debug 最低级别,一般开发用来打印一些调试信息 info ...
- 准备openstack基础环境
在所有的openstack节点上执行 1.配置阿里yum源 yum -y install wget rm -rf /etc/yum.repos.d/* wget -O /etc/yum.repos.d ...
- 在Ubuntu里搭建spark环境
注意:1.搭建环境:Ubuntu64位,Linux(也有Windows的,我还没空试) 2.一般的配置jdk.Scala等的路径环境是在/etc/profile里配置的,我自己搭建的时候发 ...
- thinkphp5 默认配置代码
<?php // +---------------------------------------------------------------------- // | ThinkPHP [ ...
- 最新 优刻得java校招面经 (含整理过的面试题大全)
从6月到10月,经过4个月努力和坚持,自己有幸拿到了网易雷火.京东.去哪儿.优刻得等10家互联网公司的校招Offer,因为某些自身原因最终选择了优刻得.6.7月主要是做系统复习.项目复盘.LeetCo ...