题目:
题目链接:[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 ;}//初始的逆序对个数都是1int 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;
}