题目传送门

题目大意

给出一个 \(n\) 个点的树,每个点有权值,从中选出一些点,使得满足大根堆的性质。(即一个点的祖先节点如果选了那么该点的祖先节点的权值一定需要大于该点权值)

问能选出来的大根堆的最大大小。\(n\le 2\times 10^5\)

线段树合并

跟尧姐一起想的。

首先不难想到 dp,我们可以设 \(f_{u,i}\) 表示 \(u\) 子树内选出顶点权值 \(\le i\) 的大根堆的最大大小。我们可以列出转移式:

\[f_{u,i}=\max\{\sum_{v\in son_u} f_{v,i},\sum_{v\in son_u} f_{v,i-1}+1\}
\]

然后我们发现这个东西似乎可以线段树合并转移。具体来说我们每次先把儿子的线段树合并起来,然后计算以当前节点为根的大根堆的最大大小。

不难发现,我们需要实现区间取 \(\max\),单点查询。这个东西如果要硬上的话似乎是不好搞的,因为线段树合并似乎不支持懒标记(此处存疑)。

接着思考,可以发现的是,我们每次修改的一定是一段区间,而且如果能够修改肯定是区间 \(+1\)。于是,我们就可以维护差分数组,然后在线段树上二分找到修改的端点即可。

时间复杂度 \(\Theta(n\log n)\),空间复杂度 \(\Theta(n\log n)\)。

\(\texttt{Code}\)

#include <bits/stdc++.h>
using namespace std; #define Int register int
#define INF 0x7f7f7f7f
#define MAXN 200005 template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');} vector <int> G[MAXN];
int n,uni,res,f[MAXN],rt[MAXN],tmp[MAXN],value[MAXN]; struct Segment{
#define MAXM MAXN*100
int cnt,son[MAXM][2],sum[MAXM];
void modify (int &x,int l,int r,int pos,int val){
if (!x) x = ++ cnt;sum[x] += val;
if (l == r) return ;
int mid = (l + r) >> 1;
if (pos <= mid) modify (son[x][0],l,mid,pos,val);
else modify (son[x][1],mid + 1,r,pos,val);
}
int query (int x,int l,int r,int ql,int qr){
if (!x) return 0;
if (ql > qr) return 0;
if (l >= ql && r <= qr) return sum[x];
int mid = (l + r) >> 1,res = 0;
if (ql <= mid) res += query (son[x][0],l,mid,ql,qr);
if (qr > mid) res += query (son[x][1],mid + 1,r,ql,qr);
return res;
}
int Merge (int x,int y){
if (!x || !y) return x + y;
sum[x] += sum[y];
son[x][0] = Merge (son[x][0],son[y][0]);
son[x][1] = Merge (son[x][1],son[y][1]);
return x;
}
int find (int x,int l,int r,int t){
if (!x) return r;
if (l == r) return l;
int mid = (l + r) >> 1;
if (t < sum[son[x][0]]) return find (son[x][0],l,mid,t);
else return find (son[x][1],mid + 1,r,t - sum[son[x][0]]);
}
}Tree; void dfs (int u){
f[u] = 1;
for (Int v : G[u]){
dfs (v);
rt[u] = Tree.Merge (rt[u],rt[v]);
}
f[u] = Tree.query (rt[u],1,uni,1,value[u] - 1) + 1;
Tree.modify (rt[u],1,uni,value[u],1);
int pos = Tree.find (rt[u],1,uni,f[u]);
if (Tree.query (rt[u],1,uni,1,pos) > f[u]) -- pos;
if (pos < uni) Tree.modify (rt[u],1,uni,pos + 1,-1);
} signed main(){
read (n);
for (Int i = 1,fa;i <= n;++ i) read (value[i],fa),G[fa].push_back (i),tmp[i] = value[i];
sort (tmp + 1,tmp + n + 1),uni = unique (tmp + 1,tmp + n + 1) - tmp - 1;
for (Int i = 1;i <= n;++ i) value[i] = lower_bound (tmp + 1,tmp + uni + 1,value[i]) - tmp;
dfs (1);write (Tree.query (rt[1],1,uni,1,uni)),putchar ('\n');
return 0;
}

set 启发式合并

by @自为风月马前卒

本质上来说是个贪心???

可以发现的是,在大根堆大小相同的时候,我们肯定想要顶点的权值尽可能小,这样就对后面合并更优。

于是,我们可以考虑把当前点与子树进行合并,我们如果子树内有比当前点更大的值,我们发现答案不会变,直接替换 set 中比它与它相邻的树即可。否则直接加进来即可。

时间复杂度 \(\Theta(n\log^2n)\),空间复杂度 \(\Theta(n)\)。

\(\texttt{Code}\)

#include<bits/stdc++.h>
#define sit multiset<int>::iterator
using namespace std;
const int MAXN = 2e5 + 10;
inline int read() {
char c = getchar(); int x = 0, f = 1;
while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
vector<int> v[MAXN];
multiset<int> s[MAXN];
int N, val[MAXN];
void dfs(int x, int fa) {
for(int i = 0; i < v[x].size(); i++) {
int to = v[x][i];
if(to == fa) continue;
dfs(to, x);
if(s[to].size() > s[x].size()) swap(s[to], s[x]);
for(sit it = s[to].begin(); it != s[to].end(); it++)
s[x].insert(*it);
s[to].clear();
}
sit it = s[x].lower_bound(val[x]);
if(it != s[x].end()) s[x].erase(it);
s[x].insert(val[x]);
}
main() {
N = read();
for(int i = 1; i <= N; i++) {
val[i] = read();
int x = read();
v[i].push_back(x); v[x].push_back(i);
}
dfs(1, 0);
printf("%d", s[1].size());
return 0;
}

题解 「BZOJ4919 Lydsy1706月赛」大根堆的更多相关文章

  1. BZOJ4919[Lydsy1706月赛]大根堆-------------线段树进阶

    是不是每做道线段树进阶都要写个题解..根本不会写 Description 给定一棵n个节点的有根树,编号依次为1到n,其中1号点为根节点.每个点有一个权值v_i. 你需要将这棵树转化成一个大根堆.确切 ...

  2. bzoj4919 [Lydsy1706月赛]大根堆

    Description 给定一棵n个节点的有根树,编号依次为1到n,其中1号点为根节点.每个点有一个权值v_i. 你需要将这棵树转化成一个大根堆.确切地说,你需要选择尽可能多的节点,满足大根堆的性质: ...

  3. BZOJ4919:[Lydsy1706月赛]大根堆(set启发式合并)

    Description 给定一棵n个节点的有根树,编号依次为1到n,其中1号点为根节点.每个点有一个权值v_i. 你需要将这棵树转化成一个大根堆.确切地说,你需要选择尽可能多的节点,满足大根堆的性质: ...

  4. BZOJ4919 [Lydsy1706月赛]大根堆 【dp + 启发式合并】

    题目链接 BZOJ4919 题解 链上的\(LIS\)维护一个数组\(f[i]\)表示长度为\(i\)的\(LIS\)最小的结尾大小 我们可以用\(multiset\)来维护这个数组,子树互不影响,启 ...

  5. BZOJ4921「Lydsy1706月赛」互质序列

    吐槽一下BZOJ没有C++11  题还是不难的 BZOJ 4921 题意 在长度为$ n$的数列中去掉非空的连续一段并保证剩下数字不少于$ 2$ 求合法的所有方案中剩下数字的最大公约数的总和 $Sol ...

  6. 题解 「BJOI2018 治疗之雨」

    题目传送门 题目大意 有一个初始为 \(p\) 的数,每次操作分为以下两个: 有 \(\frac{1}{m+1}\) 的概率$+1,但是中途 \(p\) 的最大值只能为 \(n\)$ 有 \(k\) ...

  7. 【BZOJ4919】[Lydsy六月月赛]大根堆 线段树合并

    [BZOJ4919][Lydsy六月月赛]大根堆 Description 给定一棵n个节点的有根树,编号依次为1到n,其中1号点为根节点.每个点有一个权值v_i. 你需要将这棵树转化成一个大根堆.确切 ...

  8. bzoj 4919 [Lydsy1706月赛]大根堆 set启发式合并+LIS

    4919: [Lydsy1706月赛]大根堆 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 599  Solved: 260[Submit][Stat ...

  9. [Lydsy1706月赛]大根堆

    4919: [Lydsy1706月赛]大根堆 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 358  Solved: 150[Submit][Stat ...

随机推荐

  1. vue-cli3.x中的webpack配置,优化及多页面应用开发

    官方文档 vue-cli3以下版本中,关于webpack的一些配置都在config目录文件中,可是vue-cli3以上版本中,没有了config目录,那该怎么配置webpack呢? 3.x初始化项目后 ...

  2. 【Office Excel】vlookup函数的反向查找实例教程,不只是正向查找,还可以反向查找,实例讲解

    VLOOKUP 反向查询 众所周知,vlookup只能从左向右查找,而不能从右至左的反向查找.为此高手们设计了一个让无数新手迷惑的公式.今天优爱酷将彻底帮同学们解开这个迷团. [例]如下图所示要求根据 ...

  3. AndroidJetpack Fragment之Navigation和ViewPager2

    新的Fragment导航方式:Navigation 1.创建若干个fragment 2.添加导航 1)新建Navigation:右键res文件夹,New->Android Resource Fi ...

  4. mysql绕过root密码登录

    绕过密码登录步骤: 一.Mysql8.0之前: 关闭服务 执行参数:mysqld --skip-grant-tables 新开窗口执行mysql,即可进入mysql 二.Mysql8.0之前: 关闭服 ...

  5. 详细解读go语言中的chnanel

    Channel 底层数据结构 type hchan struct { qcount uint // 当前队列中剩余元素个数 dataqsiz uint // 环形队列长度,即可以存放的元素个数 buf ...

  6. C# Equals方法和==有什么区别

    开发工具:VS2019 一.关于这两个比较,需要从值类型和引用类型两方面来说 (A)先说值类型 上图: 因为在对值类型进行比较时候,不管 .Equals() 方法还是 == 方法,都是对值类型变量(图 ...

  7. ubuntu下使用minicom

    环境 宿主机平台:Ubuntu 16.04.6 目标机:iMX6ULL 安装及使用 首先时在Ubuntu里安装minicom sudo apt-get install minicom 接下来可以使用 ...

  8. Appium问题解决方案(7)- Could not find 'adb.exe' in PATH. Please set the ANDROID_HOME environment variable with the Android SDK root directory path

    背景:运行代码提示找不到ADB An unknown server-side error occurred while processing the command. Original error: ...

  9. java多线程 synchronized 与lock锁 实现线程安全

    如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的. 通过卖火车票的例子 火车站要卖票,我们模 ...

  10. GIT:修改上一次提交的注释信息(git commit --amend)

    git commit -m 注释信息 如果这时候注释信息输入错误,就可以输入以下指令更改 git commit --amend 键入" i "进入编辑模式 修改后键入ESC,:wq ...