hihocoder 1193 树堆 解题报告
题目大意:给出一棵有根树(根为 \(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 树堆 解题报告的更多相关文章
- 【九度OJ】题目1176:树查找 解题报告
[九度OJ]题目1176:树查找 解题报告 标签(空格分隔): 九度OJ http://ac.jobdu.com/problem.php?pid=1176 题目描述: 有一棵树,输出某一深度的所有节点 ...
- HDU 1754 线段树入门解题报告
---恢复内容开始--- 题意:给定区间,每个人的成绩, Q次询问,求每次询问区间中的最大值 思路:构造线段树 代码: #include<stdio.h> #include<algo ...
- POJ 3264 线段树入门解题报告
题意:给n个值, Q次询问, 每次询问给定一个区间, 要求输出该区间最大最小值之差 思路:暴力的话每次询问都要遍历多次for循环一定会超时, 用线段树记录区间的信息(左边界右边界, 该区间最大值最小值 ...
- [NOIP2016 DAY1 T2]天天爱跑步-[差分+线段树合并][解题报告]
[NOIP2016 DAY1 T2]天天爱跑步 题面: B[NOIP2016 DAY1]天天爱跑步 时间限制 : - MS 空间限制 : 565536 KB 评测说明 : 2s Description ...
- 洛谷 P3373 【模板】线段树 2 解题报告
P3373 [模板]线段树 2 题目描述 如题,已知一个数列,你需要进行下面三种操作: 1.将某区间每一个数乘上\(x\) 2.将某区间每一个数加上\(x\) 3.求出某区间每一个数的和 输入输出格式 ...
- 洛谷 [FJOI2014]最短路径树问题 解题报告
[FJOI2014]最短路径树问题 题目描述 给一个包含\(n\)个点,\(m\)条边的无向连通图.从顶点\(1\)出发,往其余所有点分别走一次并返回. 往某一个点走时,选择总长度最短的路径走.若有多 ...
- [jzoj 3175] 数树数 解题报告 (树链剖分)
interlinkage: https://jzoj.net/senior/#main/show/3175 description: 给定一棵N 个节点的树,标号从1~N.每个点有一个权值.要求维护两 ...
- [hihocoder #1384] Genius ACM 解题报告(倍增)
题目链接:http://hihocoder.com/problemset/problem/1384 题目大意: 给定一个整数 M,对于任意一个整数集合 S,定义“校验值”如下: 从集合 S 中取出 M ...
- 【九度OJ】题目1172:哈夫曼树 解题报告
[九度OJ]题目1172:哈夫曼树 解题报告 标签(空格分隔): 九度OJ http://ac.jobdu.com/problem.php?pid=1172 题目描述: 哈夫曼树,第一行输入一个数n, ...
随机推荐
- 用css动态实现圆环百分比分配——初探css3动画
最近的小程序项目有个设计图要求做一个圆环,两种颜色分配,分别代表可用金额和冻结金额.要是就直接这么显示,感觉好像挺没水平??于是我决定做个动态! 在mdn把新特性gradients(渐变).trans ...
- ES6-11学习笔记--数组的扩展
类数组 / 伪数组 Array.from() Array.of() copyWithin() fill() includes() 类数组.伪数组例子: let divs = document.ge ...
- 软件构造实验-百度图像识别api
识别结果: 识别结果:
- java中如何使用接口继承(Extending Interfaces)
5.接口继承(Extending Interfaces)和通话talk的功能.而Moto888更为高级,除了照相和通话功能以外,还有mp3的功能.接口继承到底有什么意义呢?马克-to-win:1)通过 ...
- GitHub Pages + Hexo搭建个人博客网站-github风格-采坑记录
目录 1.本机安装nodejs 2.github上创建仓库 3.安装hexo 4.hexo主题 5.配置主题 6.添加文章 7.使用分类和标签 8.增加文章目录 9.推送github 使用github ...
- C++---使用类
运算符重载 概念 运算符重载就是想法转换, 目的是简化函数调用的方式 重载就是赋予新的含义, 运算符重载也是, 即同一个运算符可以有不同的功能 C++本身已经对一些运算符进行了重载, 同时C++允许程 ...
- 美团动态线程池实践思路开源项目(DynamicTp),线程池源码解析及通知告警篇
大家好,这篇文章我们来聊下动态线程池开源项目(DynamicTp)的通知告警模块.目前项目提供以下通知告警功能,每一个通知项都可以独立配置是否开启.告警阈值.告警间隔时间.平台等,具体代码请看core ...
- oracle 11g rac集群 asm磁盘组增加硬盘
创建asm磁盘的几种方式 创建asm磁盘方式很多主要有以下几种 1.Faking方式 2.裸设备方式 3.udev方式(它下面有两种方式) 3.1 uuid方式 3.2 raw方式(裸设备方式) 4. ...
- java数组算法——数组元素的赋值2
java数组算法--数组元素的赋值2--java经典面试题:创建一个长度为6的int型数组,要求数组元素的值都在1-30之间,且是随机赋值.同时要求元素时的值各不相同
- go的调度
操作系统根据资源访问权限的不同,体系架构可以分为用户空间和内核空间:内核空间主要操作访问CPU资源,IO资源,内存资源等硬件资源,为应用程序提供最基本的基础资源:用户空间是上层应用程序的固定活动空间, ...