【线段树】[LUOGU [POI2011] ROT-Tree Rotations] 线段树合并

题目:

题目链接:[LUOGU [POI2011] ROT-Tree Rotations]
题解:
(这此是真的线段树合并,上次是,,失误)这个题,从权值线段树开始学,又学了动态开点,最后才学的线段树合并,,,,(我说之前我连题解都看不懂呢,,,)

先说一下动态开点,我认为它就是个比较好的思想,在之前我写的线段树是中规中矩的,开满满的二叉树,还要开4倍的空间,这样的话其实是比较浪费空间的,那么就可以换一种思路,只开够需要的点,即可,这点和主席树很像,在开空间的时候就开到40倍就行了,(与主席树的原因一样,是由于是nlogn的)这样的话就可以极大的对空间的节省。

再是就说一下权值线段树,这个东西啊,还是源神给我讲懂的,这其实说的这么高大上,它,,就是个,是一个进阶的桶,可以二进制加速而已,(就因为这个,,我想了好久怎么在每个点上都建一棵权值线段树,,,,(我真是个傻子,,))

之后就再说一下线段树合并吧,(被纠出错的我,硬着头皮学了线段树合并,,)就是给你两颗线段树 x x x, y y y(有人会叫左右子树,这样的话就比较容易混淆了),(一般来说应该都是权值线段树),然后就是合并,如果没有 x x x,就返回 y y y如果没有 y y y,然后就是对普通的 x x x y y y进行合并了,这个主要因题而异吧,不知道会让求什么,,当然,对于两颗线段树的相加减: [ 1 , r ] − [ 1 , l ] = [ l + 1 , r ] [1,r]-[1,l]=[l+1,r] [1,r][1,l]=[l+1,r],可以看这道题的代码,,线段树合并一般用于对子树的统计,一般的套路就是对树的每一个节点都开上一颗动态开点线段树,然后统计子树信息时,合并所有儿子信息,统计答案,然后继续向上走;

不扯了,上正经的题解:

(不得不吐槽一下毒瘤的输入)第一个数是 n n n,其余的几个数是需要递归处理的,就是一颗树,如果是 0 0 0的话意思就是这个点之下还有两个点,如果不是 0 0 0的话,意思就是叶子节点,而且注意,输入的全部都是权值不是标号!!

这个题其实就是对线段树合并的简单应用,还有一些贪心的成分在里面,既然是可以进行两种操作,一种是交换,一种就是不交换,那就直接对这两种的逆序对个数取min即可,在然后就是对于两颗权值线段树的不断合并即可。

代码:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int sea=200100;
struct hit{int ls,rs,w;}tr[sea*40];
int v[sea*2],l[sea*2],r[sea*2],root[sea*2],n,seg,size;
LL ans,cnt1,cnt2;
void dfs(int k)
{
	v[k]=read();
	if(!v[k])
	{
		l[k]=++size; dfs(l[k]);
		r[k]=++size; dfs(r[k]);
	}
}
void build(int &k,int l,int r,int val)
{
	if(!k) k=++seg;//动态开点 
	if(l==r){tr[k].w=1; return ;}//初始的逆序对个数都是1
	int mid=(l+r)/2;
	if(val<=mid) build(tr[k].ls,l,mid,val);//权值线段树
	else build(tr[k].rs,mid+1,r,val); 
	tr[k].w=tr[tr[k].ls].w+tr[tr[k].rs].w;
}
int incor(int x,int y)//合并 
{
	if(!x) return y; if(!y) return x;
	cnt1+=(LL)tr[tr[x].ls].w*tr[tr[y].rs].w;//贪心判断
	cnt2+=(LL)tr[tr[x].rs].w*tr[tr[y].ls].w;
	//接着合并子树
	tr[x].ls=incor(tr[x].ls,tr[y].ls);  //没有交换
	tr[x].rs=incor(tr[x].rs,tr[y].rs);//交换
	tr[x].w=tr[tr[x].ls].w+tr[tr[x].rs].w;
	return x; 
}
void so(int k)
{	
	if(!k) return;	so(l[k]);so(r[k]);
	if(!v[k])
	{
		cnt1=cnt2=0;
		root[k]=incor(root[l[k]],root[r[k]]);//合并权值线段树 
		ans+=min(cnt1,cnt2);		
	}
} 
int main()
{
	n=read(); ++size;dfs(1);
	for(int i=1;i<=size;i++) if(v[i]) build(root[i],1,n,v[i]);//root[]是权值线段树的标号
	so(1); printf("%lld\n",ans);
	return 0;
}

Continue……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值