[Vani有约会]雨天的尾巴 线段树合并
[Vani有约会]雨天的尾巴
线段树合并入门好题。
先别急着上线段树合并,考虑一下这题的暴力。一看就是树上差分,对于每一个节点统计每种救济粮的数量,再一遍dfs把差分的结果统计成答案。如果暴力统计,即对于每一个点开一个数组记录每种救济粮的数量,统计时再\(O(S)\)(设\(S\)为救济粮的种类数)地合并两个节点的信息,无论时间还是空间都是无法承受的,于是考虑优化。
容易想到对于每一个点开一棵值域线段树记录区间内救济粮最多的种类和相应的个数,但是这样还是会炸,所以不能用一般的线段树,考虑使用动态开点线段树。
所谓“动态开点”,其实就是不把一棵线段树建满,而只把我们需要的节点建出来。这样做就能够把空间复杂度降到\(O(mlogS)\),因为对于每一次救济粮的发放,最坏情况下只会开出4条长度为\(logS\)的链(树上差分,修改\(x\)、\(y\)、\(lca\)和\(lca\)的父亲四处)。这样做的话,线段树上每个节点的儿子就不是\(k<<1\)和\(k<<1|1\)了,需要单独记录。
其实把动态开点线段树建出来之后,线段树合并的想法就非常自然了,在这里先口胡一下,待会可以结合代码理解。对于两棵待合并的线段树上的记录相同值域的节点,考虑合并这两个节点的信息,如果某一个节点上没有信息,合并后的节点就直接继承另一个节点的信息,并且不用往下递归,否则就往下递归处理;直到统计的区间变成一个点,就合并两个点上的信息,回溯。其实和普通线段树的操作是非常相似的。事实上,这个算法的时间复杂度是\(O(mlogS)\),因为最坏情况下有\(O(m)\)条长度为\(O(logS)\)的链需要合并,所以最多只会有\(O(mlogS)\)个节点,而每个节点的信息只会被合并一次。
代码(没有刻意卡空间):
#include<cstdio>
#include<cctype>
#define R register
#define I inline
using namespace std;
const int S=100003,N=200003,M=8000003;
char buf[S],*p1,*p2;
I char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,S,stdin),p1==p2)?EOF:*p1++;}
I int rd(){
R int f=0; R char c=gc();
while(c<48||c>57) c=gc();
while(c>47&&c<58) f=f*10+(c^48),c=gc();
return f;
}
int h[S],s[N],g[N],d[S],t[S],p[S],q[S],r[S],v[S],f[S],c,e;
struct D{int u,c;
friend int operator>(D x,D y){return x.c^y.c?x.c>y.c:x.u<y.u;}
friend D operator+(D x,D y){return (D){x.u,x.c+y.c};}
};
struct T{int l,r; D v;}o[M];
I D max(D x,D y){return x>y?x:y;}
I void swp(int &x,int &y){x^=y,y^=x,x^=y;}
I void add(int x,int y){s[++c]=h[x],h[x]=c,g[c]=y;}
I void psu(int k){o[k].v=max(o[o[k].l].v,o[o[k].r].v);}
void ins(int &k,int l,int r,D v){if(!k) k=++e;
if(l==r){o[k].v=v+o[k].v; return ;} R int m=l+r>>1;
if(v.u<=m) ins(o[k].l,l,m,v); else ins(o[k].r,m+1,r,v); psu(k);
}
int mrg(int k,int t,int l,int r){
if(!k) return t; if(!t) return k;
if(l==r){o[k].v=o[k].v+o[t].v; return k;} R int m=l+r>>1;
o[k].l=mrg(o[k].l,o[t].l,l,m),o[k].r=mrg(o[k].r,o[t].r,m+1,r),psu(k);
return k;
}
void dfs1(int x,int f){p[x]=f,d[x]=d[f]+1,t[x]=1;
for(R int i=h[x],y,m=0;i;i=s[i])
if((y=g[i])^f){dfs1(y,x),t[x]+=t[y];
if(t[y]>m) m=t[y],q[x]=y;
}
}
void dfs2(int x,int t){r[x]=t;
if(q[x]) dfs2(q[x],t);
for(R int i=h[x],y;i;i=s[i])
if(((y=g[i])^p[x])&&(y^q[x])) dfs2(y,y);
}
int lca(int x,int y){
for(;r[x]^r[y];x=p[r[x]]) if(d[r[x]]<d[r[y]]) swp(x,y);
return d[x]<d[y]?x:y;
}
void dfs(int x){
for(R int i=h[x],y;i;i=s[i])
if((y=g[i])^p[x]) dfs(y),v[x]=mrg(v[x],v[y],1,S);
f[x]=o[v[x]].v.u;
}
int main(){
R int n=rd(),m=rd(),i,x,y,z,u;
for(i=1;i<n;++i) x=rd(),y=rd(),add(x,y),add(y,x);
for(dfs1(1,0),dfs2(1,1),i=1;i<=m;++i){
x=rd(),y=rd(),z=rd(),u=lca(x,y);
ins(v[x],1,S,(D){z,1}),ins(v[y],1,S,(D){z,1}),ins(v[u],1,S,(D){z,-1});
if(p[u]) ins(v[p[u]],1,S,(D){z,-1});
}
for(dfs(1),i=1;i<=n;++i) printf("%d\n",f[i]);
return 0;
}
我不会告诉你我因为lca写挂调了几个小时
[Vani有约会]雨天的尾巴 线段树合并的更多相关文章
- 洛谷P4556 [Vani有约会]雨天的尾巴(线段树合并)
题目背景 深绘里一直很讨厌雨天. 灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切. 虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连根拔起,以及田地 ...
- 【BZOJ3307】雨天的尾巴 线段树合并
[BZOJ3307]雨天的尾巴 Description N个点,形成一个树状结构.有M次发放,每次选择两个点x,y对于x到y的路径上(含x,y)每个点发一袋Z类型的物品.完成所有发放后,每个点存放最多 ...
- BZOJ3307雨天的尾巴——线段树合并
题目描述 N个点,形成一个树状结构.有M次发放,每次选择两个点x,y对于x到y的路径上(含x,y)每个点发一袋Z类型的物品.完成所有发放后,每个点存放最多的是哪种物品. 输入 第一行数字N,M接下来N ...
- P4556 雨天的尾巴 线段树合并
使用线段树合并,每个节点维护一棵权值线段树,下标为救济粮种类,区间维护数量最多的救济粮编号(下标).所以每个节点答案即为\(tre[rot[x]]\). 然后运用树上点的差分思想,对于分发路径\(u, ...
- bzoj 3307: 雨天的尾巴 线段树合并
题目大意: N个点,形成一个树状结构.有M次发放,每次选择两个点x,y对于x到y的路径上(含x,y)每个点发一袋Z类型的物品.问完成所有发放后,每个点存放最多的是哪种物品. 题解: 首先我们为每一个节 ...
- P4556 [Vani有约会]雨天的尾巴(线段树合并+lca)
P4556 [Vani有约会]雨天的尾巴 每个操作拆成4个进行树上差分,动态开点线段树维护每个点的操作. 离线处理完向上合并就好了 luogu倍增lca被卡了5分.....于是用rmq维护.... 常 ...
- P4556 [Vani有约会]雨天的尾巴 (线段树合并)
P4556 [Vani有约会]雨天的尾巴 题意: 首先村落里的一共有n座房屋,并形成一个树状结构.然后救济粮分m次发放,每次选择两个房屋(x,y),然后对于x到y的路径上(含x和y)每座房子里发放一袋 ...
- 洛谷 P4556 [Vani有约会]雨天的尾巴 解题报告
P4556 [Vani有约会]雨天的尾巴 题目背景 深绘里一直很讨厌雨天. 灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切. 虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒 ...
- 「Luogu4556」Vani有约会-雨天的尾巴
「Luogu4556」Vani有约会-雨天的尾巴 传送门 很显然可以考虑树上差分+桶,每次更新一条链就是把这条链上的点在桶对应位置打上 \(1\) 的标记, 最后对每个点取桶中非零值的位置作为答案即可 ...
随机推荐
- Java工具类(util) 之01- 数学运算工具(精确运算)
数学运算工具(精确运算) /** * * @author maple * */ public abstract class AmountUtil { private AmountUtil() { } ...
- Kali-linux Gerix Wifi Cracker破解无线网络
Gerix Wifi Cracker是另一个aircrack图形用户界面的无线网络破解工具.本节将介绍使用该工具破解无线网络及创建假的接入点. 9.3.1 Gerix破解WEP加密的无线网络 在前面介 ...
- Hadoop 解除 NameNode is in safe mode
运行Hadoop程序时,有时候会报以下错误: org.apache.hadoop.dfs.SafeModeException: Cannot delete /user/hadoop/input. Na ...
- .net根据经纬度获取地址(百度api)
private string GetAddress(string lng, string lat) { try { string url = @"http://api.map.baidu.c ...
- ubuntu查询命令行安装的软件的安装路径
which git // 查询git的安装路径
- 查找系统中jdk的位置
查找系统中jdk的位置: [root@localhost native]# find /|grep jni_md.h /var/lib/docker/overlay2/ec7a5439382a8a6d ...
- JDK(六)JDK1.8源码分析【集合】LinkedHashMap
本文转载自joemsu,原文连接 [JDK1.8]JDK1.8集合源码阅读——LinkedHashMap LinkedHashMap的数据结构 可以从上图中看到,LinkedHashMap数据结构相比 ...
- jfinal多数据源ActiveRecordPlugin
因为项目需要从多个数据库取数据,所以需要配置多个数据源 尝试了ActiveRecordPlugin和DruidPlugin的多数据源,但是因为DruidPlugin在本地一直报错一个很奇怪的语法错误, ...
- SSAS中CUBE的多对多关系既可以出现在中间事实表上也可以出现在中间维度表上
开发过SSAS中CUBE的朋友,肯定都知道维度用法中的多对多关系, 这篇文章不想详细阐述多对多关系在CUBE中的结构,详情请在网上寻找CUBE多对多关系的介绍资料. 下面是是一个典型的CUBE中多对多 ...
- Java 8-接口的默认方法和静态方法
Java 8-接口的默认方法和静态方法 Java 8使用两个新概念扩展了接口的含义:默认方法和静态方法.默认方法使得接口有点类似traits,不过要实现的目标不一样.默认方法使得开发者可以在 不破坏二 ...