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

【BZOJ4919】[Lydsy六月月赛]大根堆

Description

给定一棵n个节点的有根树,编号依次为1到n,其中1号点为根节点。每个点有一个权值v_i。

你需要将这棵树转化成一个大根堆。确切地说,你需要选择尽可能多的节点,满足大根堆的性质:对于任意两个点i,j,如果i在树上是j的祖先,那么v_i>v_j。

请计算可选的最多的点数,注意这些点不必形成这棵树的一个连通子树。

Input

第一行包含一个正整数n(1<=n<=200000),表示节点的个数。

接下来n行,每行两个整数v_i,p_i(0<=v_i<=10^9,1<=p_i<i,p_1=0),表示每个节点的权值与父亲。

Output

输出一行一个正整数,即最多的点数。

Sample Input

6
3 0
1 1
2 1
3 1
4 1
5 1

Sample Output

5

题解:考虑用f[i][j]表示在i节点的子树中,最大值<=j,最多能选择多少点。如何转移呢?父亲节点的f数组可以看成儿子节点的f数组对应位置相加。然后再用 当前点权值-1处的f值 +1 来更新当前点权值后面的所有f值。

为此,我们可以考虑用线段树+标记永久化维护,我们要实现的有:区间加,维护区间最大值。然后转移的时候可以直接用线段树合并搞定。细节还是比较多的。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=200010;
int n,m,cnt,tot,ans;
int to[maxn],next[maxn],head[maxn],p[maxn],val[maxn],v[maxn],rt[maxn];
inline void add(int a,int b)
{
	to[cnt]=b,next[cnt]=head[a],head[a]=cnt++;
}
bool cmp(const int &a,const int &b)
{
	return val[a]<val[b];
}
struct sag
{
	int ls,rs,tag,sum;
}s[maxn<<6];
inline void pushdown(int x)
{
	if(s[x].ls)	s[s[x].ls].sum+=s[x].sum,s[s[x].ls].tag=max(s[s[x].ls].tag+s[x].sum,s[x].tag);
	if(s[x].rs)	s[s[x].rs].sum+=s[x].sum,s[s[x].rs].tag=max(s[s[x].rs].tag+s[x].sum,s[x].tag);
	s[x].sum=0;
}
int merge(int a,int b)
{
	if(!a||!b)	return a^b;
	pushdown(a),pushdown(b);
	if(!s[a].ls)	s[a].ls=s[b].ls,s[s[a].ls].tag+=s[a].tag,s[s[a].ls].sum+=s[a].tag+s[a].sum;
	else	if(!s[b].ls)	s[s[a].ls].tag+=s[b].tag,s[s[a].ls].sum+=s[b].tag+s[b].sum;
	else	s[a].ls=merge(s[a].ls,s[b].ls);
	if(!s[a].rs)	s[a].rs=s[b].rs,s[s[a].rs].tag+=s[a].tag,s[s[a].rs].sum+=s[a].tag+s[a].sum;
	else	if(!s[b].rs)	s[s[a].rs].tag+=s[b].tag,s[s[a].rs].sum+=s[b].tag+s[b].sum;
	else	s[a].rs=merge(s[a].rs,s[b].rs);
	s[a].tag+=s[b].tag;
	return a;
}
void updata(int l,int r,int &x,int a,int b,int c)
{
	if(!x)	x=++tot;
	if(a<=l&&r<=b)
	{
		s[x].tag=max(s[x].tag,c);
		return ;
	}
	pushdown(x);
	int mid=(l+r)>>1;
	if(a<=mid)	updata(l,mid,s[x].ls,a,b,c);
	if(b>mid)	updata(mid+1,r,s[x].rs,a,b,c);
}
int query(int l,int r,int x,int a)
{
	if(!x||!a)	return 0;
	if(l==r)	return s[x].tag;
	pushdown(x);
	int mid=(l+r)>>1;
	if(a<=mid)	return max(s[x].tag,query(l,mid,s[x].ls,a));
	return max(s[x].tag,query(mid+1,r,s[x].rs,a));
}
void dfs(int x)
{
	for(int i=head[x];i!=-1;i=next[i])	dfs(to[i]),rt[x]=merge(rt[x],rt[to[i]]);
	updata(1,m,rt[x],v[x],m,query(1,m,rt[x],v[x]-1)+1);
}
void find(int x)
{
	ans=max(ans,s[x].tag);
	pushdown(x);
	if(s[x].ls)	find(s[x].ls);
	if(s[x].rs)	find(s[x].rs);
}
inline int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<‘0‘||gc>‘9‘)	{if(gc==‘-‘)	f=-f;	gc=getchar();}
	while(gc>=‘0‘&&gc<=‘9‘)	ret=ret*10+(gc^‘0‘),gc=getchar();
	return ret*f;
}
int main()
{
	n=rd();
	int i,a;
	memset(head,-1,sizeof(head));
	for(i=1;i<=n;i++)
	{
		val[i]=rd(),a=rd(),p[i]=i;
		if(i!=1)	add(a,i);
	}
	sort(p+1,p+n+1,cmp);
	for(i=1;i<=n;i++)
	{
		if(i==1||val[p[i]]>val[p[i-1]])	m++;
		v[p[i]]=m;
	}
	dfs(1),find(rt[1]);
	printf("%d",ans);
	return 0;
}
时间: 2024-08-30 01:16:43

【BZOJ4919】[Lydsy六月月赛]大根堆 线段树合并的相关文章

【BZOJ4919】[Lydsy六月月赛]大根堆

题解: 首先裸的dp很好想 f[i][j]表示在i点,最大值<=j的点数最大值 看了别人的题解知道了可以用线段树合并来优化这个东西.. 我们考虑对于每个点,首先我们要合并它的子树 其实就是对于相同位置的点相加即可 然后考虑当前节点,我们应用f[v[x]-1]+1去更新v[x]-n之间的值(也就是取max操作) 然后取max这个东西是可以标记永久化的 原文地址:https://www.cnblogs.com/yinwuxiao/p/9060320.html

bzoj 4919: [Lydsy六月月赛]大根堆

Description 给定一棵n个节点的有根树,编号依次为1到n,其中1号点为根节点.每个点有一个权值v_i. 你需要将这棵树转化成一个大根堆.确切地说,你需要选择尽可能多的节点,满足大根堆的性质:对于任意两个点i,j,如果i在树上是j的祖先,那么v_i>v_j. 请计算可选的最多的点数,注意这些点不必形成这棵树的一个连通子树. Solution 思路比较直接 设 \(f[i][j]\) 表示\(i\)为子树的节点中,堆中最大值小于\(j\)的情况下能选的最多点数 转移时就是用一个前缀最大值更

[BZOJ4920][Lydsy六月月赛]薄饼切割

[BZOJ4920][Lydsy六月月赛]薄饼切割 试题描述 有一天,tangjz 送给了 quailty 一张薄饼,tangjz 将它放在了水平桌面上,从上面看下去,薄饼形成了一个 \(H \times W\) 的长方形. tangjz 交给了 quailty 一根木棍,要求 quailty 将木棍轻轻放到桌面上.然后 tangjz 会以薄饼中心作为原点,将木棍绕着原点旋转一圈,将木棍扫过的部分切下来送给 quailty. quailty 已经放好了木棍,请写一个程序帮助他们计算 quailt

bzoj 4921: [Lydsy六月月赛]互质序列

4921: [Lydsy六月月赛]互质序列 Time Limit: 1 Sec  Memory Limit: 256 MBSubmit: 188  Solved: 110[Submit][Status][Discuss] Description 你知道什么是"互质序列"吗?那就是所有数的最大公约数恰好为1的序列. "互质序列"非常容易找到,但是我们可以尝试通过删除这个序列的一个非空连续子序列来扩大它的最大公约数. 现在给定一个长度为n的序列,你需要从中删除一个非空连

启发式合并(堆、set、splay、treap)/线段树合并学习小记

启发式合并 刚听到这个东西的时候,我是相当蒙圈的.特别是"启发式"这三个字莫名的装逼,因此之前一直没有学. 实际上,这个东西就是一个SB贪心. 以堆为例,若我们要合并两个堆a.b,我们有一种极其简单的做法:那就是比较一下它们的大小,将小的堆的每个元素依次插入到大的堆中.不妨设\(|a|≤|b|\),则时间复杂度即为:\(O(|a|*log_2(|a|+|b|))\). 这个东西看似很慢,但当点数较小的时候,我们可以证明复杂度是可被接受的. 比如我们要合并n个堆,这n个堆共有m个点.设这

【BZOJ2733】永无乡[splay启发式合并or线段树合并]

题目大意:给你一些点,修改是在在两个点之间连一条无向边,查询时求某个点能走到的点中重要度第k大的点.题目中给定的是每个节点的排名,所以实际上是求第k小:题目求的是编号,不是重要度的排名.我一开始差点被这坑了. 网址:http://www.lydsy.com/JudgeOnline/problem.php?id=2733 这道题似乎挺经典的(至少我看许多神犇很早就做了这道题).这道题有两种写法:并查集+(splay启发式合并or线段树合并).我写的是线段树合并,因为--splay不会打+懒得学.

bzoj 2212 : [Poi2011]Tree Rotations (线段树合并)

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=2212 思路:用线段树合并求出交换左右儿子之前之后逆序对的数量,如果数量变小则交换. 实现代码: #include<bits/stdc++.h> using namespace std; #define ll long long const int M = 4e5+10; int n,cnt,idx; ll ans,cnt1,cnt2; int v[M],l[M],r[M],root[

【bzoj2333 &amp; luoguP3273】棘手的操作(线段树合并)

题目传送门:bzoj2333 luoguP3273 这操作还真“棘手”..听说这题是可并堆题?然而我不会可并堆.于是我就写了线段数合并,然后调了一晚上,数据结构毁一生!!!QAQ…… 其实这题也可以把合并强行看成树上的关系然后dfs序后直接线段树的,然而我菜啊..看到连边就只能想到线段树合并. 首先用并查集维护图的连通性,然后对于每个连通块建一棵下标为点的编号的线段树,于是: U=合并两棵树: A1:单点加: A2:区间加: A3:因为是整体加,所以我们可以维护一个delta表示当前每个数被整体

[树上差分][线段树合并]JZOJ 3397 雨天的尾巴

Description 深绘里一直很讨厌雨天. 灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切. 虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连 根拔起,以及田地里的粮食被弄得一片狼藉. 无奈的深绘里和村民们只好等待救济粮来维生. 不过救济粮的发放方式很特别. 首先村落里的一共有n 座房屋,并形成一个树状结构.然后救济粮分m 次发放,每次选择 两个房屋(x,y),然后对于x 到y 的路径上(含x 和y) 每座房子里发放一袋z 类型的救济粮.