题目大意:给出一棵有根树(根为 \(0\) ),点有点权。可以删除点(非根),并将其子树接到其父亲上。我们称一个树为树堆当前仅当树上每个点都满足其权值大于等于其子树中所有点的点权。现在对于每个点要求其子树删去一点后形成的树堆的最大大小,其中这个点在其自身的子问题内是不可删除的。

题解

这个每个点的子树都求一遍一眼就可以看出是 线段树合并 或者 dsu on tree ,然后略加思索发现这个维护的过程类似 DP ,可以发现不太适合 dsu on tree ,因此选择 线段树合并 。现在考虑怎么维护。

首先我们将点权离散化,然后每个点开一个线段树用线段树的每个位置维护点权为 \(k\) 的值加在当前点的父亲上(这个新加的点不可删除)时可以得到的最大子树大小。那么对于一个点 \(u\),我们将其子树的线段树全部合并起来,具体来说是线段树的每个节点的权值相加(因为各个子树互不干扰)。点 \(u\) 的答案直接查询其合并完的线段树中 \(val[u]\) 位置的值 \(+1\) 即可得到。但是之后向上合并的时候这个点是可以被删除了,这个要怎么维护?

发现当前的线段树维护的是没加入当前点的,也就是说它维护的还是对于每个权值 \(k\) 其子树可以得到的最大大小,只需要以合适的方法加入 \(u\) 的信息即可。如果上方接入父亲的节点的权值比当前点的大,那么显然可以拿到这个点的答案,但是也可能不选择这个点更优,我们只需要对于线段树的从 \(val[u]\) 开始的后缀的这段区间的所有点对于 \(ans[u]\) 取 \(max\) 即可,但是 \(max\) 和 \(add\) 的标记同时维护起来巨难,无法知晓标记先后顺序。但是不用怕,我们可以把取 \(max\) 转换成加法。发现 \(ans[u]\) 是之前的线段树查询出的结果 \(+1\) ,即线段树上 \(val[u]\) 位置上的值是 \(ans[u]-1\) ,那么我们只需要找到最后一个线段树中权值为 \(ans[u]\) 的位置 \(r\),把区间 \([val[u],r)\) 进行区间 \(+1\) 即可。(因为我们取 \(max\) 是从 \(val[u]\) 这个位置开始的)

具体一些实现细节看代码吧:

#define ll long long
#define db double
#define filein(a) freopen(#a".in","r",stdin)
#define fileot(a) freopen(#a".out","w",stdout)
#define sky fflush(stdout);
#define gc getchar
#define pc putchar
namespace IO{
inline bool blank(const char &c){
return c==' ' or c=='\n' or c=='\t' or c=='\r' or c==EOF;
}
inline void gs(char *s){
char ch=gc();
while(blank(ch) ) {ch=gc();}
while(!blank(ch) ) {*s++=ch;ch=gc();}
*s=0;
}
inline void gs(std::string &s){
char ch=gc();s+='#';
while(blank(ch) ) {ch=gc();}
while(!blank(ch) ) {s+=ch;ch=gc();}
}
inline void ps(char *s){
while(*s!=0) pc(*s++);
}
inline void ps(const std::string &s){
for(auto it:s)
if(it!='#') pc(it);
}
template<class T>
inline void read(T &s){
s=0;char ch=gc();bool f=0;
while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=gc();}
while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=gc();}
if(ch=='.'){
db p=0.1;ch=gc();
while('0'<=ch&&ch<='9') {s=s+p*(ch^48);p*=0.1;ch=gc();}
}
s=f?-s:s;
}
template<class T,class ...A>
inline void read(T &s,A &...a){
read(s);read(a...);
}
};
using IO::read;
using IO::gs;
using IO::ps;
const int N=1e5+3;
int n;
int a[N];
namespace Discrete{
int id[N];
inline void work(){
for(int i=1;i<=n;++i){
id[i]=i;
}
std::sort(id+1,id+1+n,[](int x,int y){
return a[x]<a[y];
});
int la=-1e9,top=0;
for(int i=1;i<=n;++i){
if(a[id[i] ]!=la) ++top;
la=a[id[i] ];
a[id[i] ]=top;
}
}
};
int head[N],nxt[N<<1];
struct Edge{
int u,v;
}to[N<<1];
int Etot=-1;
inline void link(int u,int v){
nxt[++Etot]=head[u];
head[u]=Etot;
to[Etot]={u,v};
}
inline void link1(int u,int v){
link(u,v);link(v,u);
}
struct SegTree{
int rt[N];
int tot;
#define lc(x) t[x].lc
#define rc(x) t[x].rc
struct node{
int lc,rc;
int mx;
int add;
}t[N*40];
inline void pushup(int x){
t[x].mx=std::max(t[lc(x)].mx,t[rc(x)].mx);
}
inline bool leaf(int x){
return !lc(x) and !rc(x);
}
inline void change(int x,int add){
if(!x) return;
t[x].add+=add;t[x].mx+=add;
}
inline void pushdown(int x){
if(t[x].add){
if(!lc(x) ) lc(x)=++tot;
if(!rc(x) ) rc(x)=++tot;
change(lc(x),t[x].add);
change(rc(x),t[x].add);
t[x].add=0;
}
}
void modify(int &x,int l,int r,int ql,int qr,int k){
if(!x){
x=++tot;
t[x].mx=t[x].add=0;
t[x].lc=t[x].rc=0;
}
if(ql<=l and r<=qr){
change(x,k);
return;
}
pushdown(x);
int mid=(l+r)>>1;
if(ql<=mid) modify(lc(x),l,mid,ql,qr,k);
if(mid+1<=qr) modify(rc(x),mid+1,r,ql,qr,k);
pushup(x);
}
int merge(int x,int y){
if(!x or !y) return x|y;
if(leaf(x) ) std::swap(x,y);
if(leaf(y) ){
change(x,t[y].mx);
return x;
}
pushdown(x);pushdown(y);
lc(x)=merge(lc(x),lc(y) );
rc(x)=merge(rc(x),rc(y) );
pushup(x);
return x;
}
int find(int x,int l,int r,int p){
if(leaf(x) ){
return t[x].mx;
}
pushdown(x);
int mid=(l+r)>>1;
if(p<=mid) return find(lc(x),l,mid,p);
else return find(rc(x),mid+1,r,p);
}
int pos(int x,int l,int r,int p){
if(t[x].mx<p) return r+1;
if(leaf(x) ) return l;
pushdown(x);
int mid=(l+r)>>1;
if(p<=t[lc(x)].mx) return pos(lc(x),l,mid,p);
else return pos(rc(x),mid+1,r,p);
}
}t;
int ans[N];
void dfs(int u,int f){
for(int i=head[u];~i;i=nxt[i]){
int v=to[i].v;
if(v==f) continue;
dfs(v,u);
t.rt[u]=t.merge(t.rt[u],t.rt[v]);
}
ans[u]=t.find(t.rt[u],1,n,a[u])+1;
int r=t.pos(t.rt[u],1,n,ans[u])-1;
t.modify(t.rt[u],1,n,a[u],r,1);
}
int main(){
filein(treap);fileot(treap);
read(n);
memset(head,-1,sizeof(head) );
for(int i=1;i<=n;++i){
read(a[i]);
}
for(int i=1;i<n;++i){
int u,v;
read(u,v);
++u;++v;
link1(u,v);
}
Discrete::work();
dfs(1,1);
for(int i=1;i<=n;++i){
printf("%d ",ans[i]);
}
return 0;
}

主要是看到网上的题解讲两句就结束了,很多东西只字未提。只觉得这样的题解没有意义,纯粹是浪费时间贴个代码大可不必。于是我就发了一篇详细的。

hihocoder 1193 树堆 解题报告的更多相关文章

  1. 【九度OJ】题目1176:树查找 解题报告

    [九度OJ]题目1176:树查找 解题报告 标签(空格分隔): 九度OJ http://ac.jobdu.com/problem.php?pid=1176 题目描述: 有一棵树,输出某一深度的所有节点 ...

  2. HDU 1754 线段树入门解题报告

    ---恢复内容开始--- 题意:给定区间,每个人的成绩, Q次询问,求每次询问区间中的最大值 思路:构造线段树 代码: #include<stdio.h> #include<algo ...

  3. POJ 3264 线段树入门解题报告

    题意:给n个值, Q次询问, 每次询问给定一个区间, 要求输出该区间最大最小值之差 思路:暴力的话每次询问都要遍历多次for循环一定会超时, 用线段树记录区间的信息(左边界右边界, 该区间最大值最小值 ...

  4. [NOIP2016 DAY1 T2]天天爱跑步-[差分+线段树合并][解题报告]

    [NOIP2016 DAY1 T2]天天爱跑步 题面: B[NOIP2016 DAY1]天天爱跑步 时间限制 : - MS 空间限制 : 565536 KB 评测说明 : 2s Description ...

  5. 洛谷 P3373 【模板】线段树 2 解题报告

    P3373 [模板]线段树 2 题目描述 如题,已知一个数列,你需要进行下面三种操作: 1.将某区间每一个数乘上\(x\) 2.将某区间每一个数加上\(x\) 3.求出某区间每一个数的和 输入输出格式 ...

  6. 洛谷 [FJOI2014]最短路径树问题 解题报告

    [FJOI2014]最短路径树问题 题目描述 给一个包含\(n\)个点,\(m\)条边的无向连通图.从顶点\(1\)出发,往其余所有点分别走一次并返回. 往某一个点走时,选择总长度最短的路径走.若有多 ...

  7. [jzoj 3175] 数树数 解题报告 (树链剖分)

    interlinkage: https://jzoj.net/senior/#main/show/3175 description: 给定一棵N 个节点的树,标号从1~N.每个点有一个权值.要求维护两 ...

  8. [hihocoder #1384] Genius ACM 解题报告(倍增)

    题目链接:http://hihocoder.com/problemset/problem/1384 题目大意: 给定一个整数 M,对于任意一个整数集合 S,定义“校验值”如下: 从集合 S 中取出 M ...

  9. 【九度OJ】题目1172:哈夫曼树 解题报告

    [九度OJ]题目1172:哈夫曼树 解题报告 标签(空格分隔): 九度OJ http://ac.jobdu.com/problem.php?pid=1172 题目描述: 哈夫曼树,第一行输入一个数n, ...

随机推荐

  1. 关于个人开源项目(vue app)的一些总结

    关于个人开源项目(vue app)的一些总结 项目地址 https://github.com/BYChoo/record 项目简介 此项目名叫:Record.是以Vue全家桶(vue,vue-rout ...

  2. ES6-11学习笔记--数组遍历

    ES5中数组遍历方式: for循环 forEach():没有返回值,只是针对每个元素调用func map():返回新的Array,每个元素为调用func的结果 filter():返回符合func条件的 ...

  3. ES6-11学习笔记--let

    新声明方式:let 1.不属于顶层对象 window 2.不允许重复声明 3.不存在变量提升 4.暂时性死区 5.块级作用域   原来var声明: var a = 5; console.log(a); ...

  4. Android控件设置半透明+EditText设置默认值+ 控件居中

    Android控件设置半透明 效果 代码: android:background="#50FFFFFF" 50表示50%透明 Android:EditText设置默认值 andro ...

  5. uView的DatetimePicker详解

    uView UI号称: 是全面兼容nvue的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水 亲身感受,用起来真的坑太多, 官方文档太简洁, 很多配置都没说明也没代码, 上百度查 ...

  6. video踩坑

    查看以及修改video控件样式,原文地址:https://blog.csdn.net/z2181745/article/details/82531686 chrome浏览器,F12调出控制台左上角三点 ...

  7. 自己对kmp算法的理解,借由 28. 实现 strStr() 为例

    做题思路 or 感想 : 就借由这道题来理解一下kmp算法吧 kmp算法的操作过程我觉得有句话很合适 :KMP 算法永不回退 目标字符串 的指针 i,不走回头路(不会重复扫描 目标字符串),而是借助 ...

  8. 取地址与解引用 C指针浅析

    C语言指针入门需要掌握的两个概念就是取地址&和解引用*,下面我们按例子来理解这两个符号的使用. int main() { int a = 0; int* pa = &a;//取地址操作 ...

  9. linux – tty,ttyS,pts,ptmx,vcs,vcsa设备文件之间的区别?(/dev/tty等)

    linux – tty,ttyS,pts,ptmx,vcs,vcsa设备文件之间的区别? 终端有字符终端和图形终端两种模式.在linux的图形环境下,我们可以通过鼠标点击来完成所有的管理任务,这是图形 ...

  10. XStream使用记录

    XStream使用记录 官网 http://x-stream.github.io/index.html 下载地址 http://x-stream.github.io/download.html 参考资 ...