BZOJ4919 大根堆(树形dp+线段树合并)
用 multiset 启发式合并贪心维护 LIS 的做法就不多说了,网上题解一大堆,着重讲一下线段树合并维护 \(dp\)。
\(O(n^2)\) 的 \(dp\) 非常显然。离散化后,设 \(dp[u][i]\) 表示节点 \(u\) 的子树中,最大值为 \(i\) 时最多取多少个节点。转移时考虑是否将节点 \(u\) 加入大根堆并分类讨论。
这样的状态不支持快速合并。考虑优化状态,设 \(dp[u][i]\) 表示节点 \(u\) 的子树中,最大值 \(\le i\) 时最多取多少个节点,并尝试使用线段树合并来维护。
转移同样考虑两种情况。如果 \(u\) 不取,那么直接 \(dp[u][i] = \sum dp[v][i]\) 即可,将 \(u\) 的儿子的线段树合并。合并之后,如果 \(u\) 取,就要求子树中取的最大值都 \(\lt val[u]\),那么要用 \(\max\limits_{i \lt val[u]}dp[u][i]+1\) 去更新 \(dp[u][\ge val[u]]\)。
注意到,根据状态的设计,\(dp[u]\) 其实是一个单调不减的序列,所以 \(\max\limits_{i \lt val[u]} dp[u][i]+1\) 其实相当于 \(dp[u][val[u]-1]+1\)。同时由于 \(dp[u]\) 单调不减,所以 \(dp[u][\ge val[u]]\) 中会被更新的值是一段左端点为 \(val[u]\) 的区间,这段区间的值都是 \(dp[u][val[u]-1]\)。
于是,可以二分出区间的右端点。具体地,二分找到最后一个 \(i\) 使得 \(dp[u][i] \lt dp[u][val[u]-1]+1\),然后在线段树上将区间 \([val[u],i]\) 覆盖或直接 \(+1\) 即可,需要标记永久化。
综上所述,该算法的时间复杂度为 \(O(n \log^2 n)\)。
/**
* @file: BZOJ4919.cpp
* @author: yaoxi-std
* @url:
*/
#pragma GCC optimize ("O2")
#pragma GCC optimize ("Ofast", "inline", "-ffast-math")
#pragma GCC target ("avx,sse2,sse3,sse4,mmx")
#include <bits/stdc++.h>
using namespace std;
#define resetIO(x) \
freopen(#x ".in", "r", stdin), freopen(#x ".out", "w", stdout)
#define debug(fmt, ...) \
fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
template <class _Tp>
inline _Tp& read(_Tp& x) {
bool sign = false; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) sign |= (ch == '-');
for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + (ch ^ 48);
return sign ? (x = -x) : x;
}
template <class _Tp>
inline void write(_Tp x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) write(x / 10);
putchar((x % 10) ^ 48);
}
bool m_be;
using ll = long long;
const int MAXN = 2e5 + 10;
const int INF = 0x3f3f3f3f;
int n, m, a[MAXN], fa[MAXN], val[MAXN];
vector<int> g[MAXN];
struct SegmentTree {
struct Node {
int ls, rs, sum;
} nd[MAXN * 25];
int tot, rt[MAXN];
int& operator[](int i) { return rt[i]; }
void update(int& i, int l, int r, int ql, int qr, int v) {
if (ql > qr) return;
if (!i) i = ++tot;
if (ql <= l && r <= qr) return void(nd[i].sum += v);
int mid = (l + r) >> 1;
if (ql <= mid) update(nd[i].ls, l, mid, ql, qr, v);
if (qr > mid) update(nd[i].rs, mid + 1, r, ql, qr, v);
}
int query(int i, int l, int r, int p) {
if (!i || !p) return 0;
if (l == r) return nd[i].sum;
int mid = (l + r) >> 1;
if (p <= mid) return nd[i].sum + query(nd[i].ls, l, mid, p);
return nd[i].sum + query(nd[i].rs, mid + 1, r, p);
}
void merge(int& x, int y, int l, int r) {
if (!x || !y) return void(x = x | y);
if (l == r) return void(nd[x].sum += nd[y].sum);
int mid = (l + r) >> 1;
merge(nd[x].ls, nd[y].ls, l, mid);
merge(nd[x].rs, nd[y].rs, mid + 1, r);
nd[x].sum += nd[y].sum;
}
} sgt;
void dfs(int u) {
for (auto v : g[u]) dfs(v), sgt.merge(sgt[u], sgt[v], 1, m);
int tmp = sgt.query(sgt[u], 1, m, a[u] - 1) + 1;
int l = a[u], r = m, pos = a[u] - 1;
while (l <= r) {
int mid = (l + r) >> 1;
if (sgt.query(sgt[u], 1, m, mid) < tmp)
l = mid + 1, pos = mid;
else
r = mid - 1;
}
sgt.update(sgt[u], 1, m, a[u], pos, 1);
}
bool m_ed;
signed main() {
read(n);
for (int i = 1; i <= n; ++i)
read(a[i]), read(fa[i]), val[i] = a[i], g[fa[i]].push_back(i);
sort(val + 1, val + n + 1), m = unique(val + 1, val + n + 1) - val - 1;
for (int i = 1; i <= n; ++i) a[i] = lower_bound(val + 1, val + m + 1, a[i]) - val;
dfs(1), write(sgt.query(1, 1, m, m)), putchar('\n');
return 0;
}
BZOJ4919 大根堆(树形dp+线段树合并)的更多相关文章
- 【洛谷5298】[PKUWC2018] Minimax(树形DP+线段树合并)
点此看题面 大致题意: 有一棵树,给出每个叶节点的点权(互不相同),非叶节点\(x\)至多有两个子节点,且其点权有\(p_x\)的概率是子节点点权较大值,有\(1-p_x\)的概率是子节点点权较小值. ...
- BZOJ4919 大根堆(动态规划+treap+启发式合并)
一个显然的dp是设f[i][j]为i子树内权值<=j时的答案,则f[i][j]=Σf[son][j],f[i][a[i]]++,f[i][a[i]+1~n]对其取max.这样是可以线段树合并的, ...
- 【pkuwc2018】 【loj2537】 Minmax DP+线段树合并
今年年初的时候参加了PKUWC,结果当时这一题想了快$2h$都没有想出来.... 哇我太菜啦.... 昨天突然去搜了下哪里有题,发现$loj$上有于是就去做了下. 结果第一题我5分钟就把所有细节都想好 ...
- [BZOJ5461][LOJ#2537[PKUWC2018]Minimax(概率DP+线段树合并)
还是没有弄清楚线段树合并的时间复杂度是怎么保证的,就当是$O(m\log n)$吧. 这题有一个显然的DP,dp[i][j]表示节点i的值为j的概率,转移时维护前缀后缀和,将4项加起来就好了. 这个感 ...
- BZOJ.5461.[PKUWC2018]Minimax(DP 线段树合并)
BZOJ LOJ 令\(f[i][j]\)表示以\(i\)为根的子树,权值\(j\)作为根节点的概率. 设\(i\)的两棵子树分别为\(x,y\),记\(p_a\)表示\(f[x][a]\),\(p_ ...
- LOJ2537. 「PKUWC2018」Minimax【概率DP+线段树合并】
LINK 思路 首先暴力\(n^2\)是很好想的,就是把当前节点概率按照权值大小做前缀和和后缀和然后对于每一个值直接在另一个子树里面算出贡献和就可以了,注意乘上选最大的概率是小于当前权值的部分,选最小 ...
- [PKUWC2018]Minimax [dp,线段树合并]
好妙的一个题- 我们设 \(f_{i,j}\) 为 \(i\) 节点出现 \(j\) 的概率 设 \(l = ch[i][0] , r = ch[i][1]\) 即左儿子右儿子 设 \(m\) 为叶子 ...
- P6847-[CEOI2019]Magic Tree【dp,线段树合并】
正题 题目链接:https://www.luogu.com.cn/problem/P6847 题目大意 \(n\)个点的一棵树上,每个时刻可以割掉一些边,一些节点上有果实表示如果在\(d_i\)时刻这 ...
- POJ 3162 Walking Race 树形DP+线段树
给出一棵树,编号为1~n,给出数m 漂亮mm连续n天锻炼身体,每天会以节点i为起点,走到离i最远距离的节点 走了n天之后,mm想到知道自己这n天的锻炼效果 于是mm把这n天每一天走的距离记录在一起,成 ...
随机推荐
- Linux 下指定端口开放访问权限
Linux 下指定端口开放访问权限 作者:Grey 原文地址: 博客园:Linux 下指定端口开放访问权限 CSDN:Linux 下指定端口开放访问权限 环境 CentOS 系和 Debian 系的防 ...
- Typora设置代码块Mac风格三个圆点
写作不停,美化不止! mac小圆点效果 原本代码块样式就挺....干净的,光秃秃的,太单调了: 是吧很丑,于是自己发挥改成了这样: 好吧还是太单调,也没好看到哪里去,于是隔了两天又重新改,DuangD ...
- Linux系统安装宝塔面板教程
# Linux系统宝塔安装教程 注意:安装宝塔面板的前提条件 首先要有一台服务器或者使用linux系统的虚拟机. 安装前请确保是[全新的机器].必须是没装过其它环境的新系统,如Apache/Nginx ...
- python的微积分运算
import sympy sympy.init_printing() from sympy import I, pi, oo import numpy as np 求函数的导数 x = sympy.S ...
- 浅尝 ECDHE 协议流程
前言 ECDHE 我之前是听都没听过, 但是新业务需要对前后端通信进行加密, 经过大佬推荐才知道有这个东西, 经过几天的学习和踩坑, 才大致明白其流程和使用方式. 过程坎坷, 好在最后还是成功运用到了 ...
- python3使用libpcap库进行抓包及数据处理
python版本:python 3.9 libpcap版本:1.11.0b7 python libpcap库是底层绑定c语言libpcap库的开发包,旨在提供python应用可访问的unix c li ...
- Java开发学习(三十九)----SpringBoot整合mybatis
一.回顾Spring整合Mybatis Spring 整合 Mybatis 需要定义很多配置类 SpringConfig 配置类 导入 JdbcConfig 配置类 导入 MybatisConfig ...
- 洛谷 P6573 [BalticOI 2017] Toll 题解
Link 算是回归OI后第一道自己写的题(考CSP的时候可没回归) 写篇题解纪念一下 题目大意: \(n\) 个点,\(m\) 条单向边,每条边的两端点 \(x\),\(y\)必定满足 \(\left ...
- Spring Boot 中使用 tkMapper
说明:基于 MyBatis 有很多第三方功能插件,这些插件可以完成数据操作方法的封装.数据库逆向工程的生成等. tkMapper 和 MyBatis-plus 都是基于 MyBatis 提供的第三方插 ...
- 陪你去看 Lodash.js 起步
lodash 起步(数组) Lodash 是一个较为流行的 JavaScript 的实用工具库. 在开发过程中如果能熟练使用一些工具库提供的方法,有利于提高开发效率. 笔者从 API 上入手,不分析其 ...