[BZOJ3702][BZOJ2212]-线段树合并

说在前面

第二次写线段树合并=w=
但是对复杂度还不是很明白。看到zyf2000的blog里有提到:深度是 log(N) N 条链,时间复杂度上限是Nlog(N)的,然后空间复杂度不超过时间复杂度…暂时感性理解一下,以后有时间了去看看证明

写的时候状态不怎么好,把Insert函数里一个小于等于写成了大于等于,用printf查了好久才发现…


题目

BZOJ3702传送门
BZOJ2212传送门
(没错,这是一道双倍经验题)

题面

现在有一棵二叉树,所有非叶子节点都有两个孩子。在每个叶子节点上有一个权值(有n个叶子节点,满足这些权值为1..n的一个排列)。可以任意交换每个非叶子节点的左右孩子。
要求进行一系列交换,使得最终所有叶子节点的权值按照中序遍历写出来,逆序对个数最少。

输入输出格式

输入格式:
第一行一个整数N,表示叶子节点个数。
下面每行包含一个整数x
如果x为0,表示这个节点非叶子节点,递归地向下读入其左孩子和右孩子的信息。
如果x不为0,表示这个节点是叶子节点,且该节点权值为x。

输出格式:
输出交换后最少的逆序对个数


解法

算是一道比较基础的线段树合并题
注意到题目说是可以随意交换子树的。那么对于一个非叶子节点u来说(假设两个儿子是x和y,并且x和y均已处理完毕),x和y要么保持原样要么交换,把这两种方式的代价取min累加到答案里就可以了,而不需要真正的去交换子树,因为子树内的顺序对子树之间的贡献是没有影响的(me略傻,这里想了很久才想明白=A=)。

那么计算代价的话用值域线段树
如果把x放在y的左边,那么对答案有贡献的部分就是x->Rsize y->Lsize
如果把y放在x的左边,那么对答案有贡献的部分就是y->Rsize x->Lsize
在合并线段树递归的时候顺便计算


下面是自带大常数的代码

调试信息懒得删除了…
需要看代码的话,还是复制到本地的编译器上看吧=w=

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int N ;
struct Tree{
    int val ;
    Tree *ls , *rs ;
}tc[400005] , *ttc = tc , *treeRoot;
struct Node{
    long long siz ;
    Node *ls , *rs ;
    void update(){
        siz = 0 ;
        if( ls ) siz += ls->siz ;
        if( rs ) siz += rs->siz ;
    }
}*root[400005] , w[400005*20] , *tw = w ;

void readTree( Tree *&nd ){
    if( !nd ) nd = ++ttc ;
    scanf( "%d" , &nd->val ) ;
    if( nd->val ) return ;
    readTree( nd->ls ) ;
    readTree( nd->rs ) ;
}

void Insert( Node *&nd , int lf , int rg , int val ){
    if( !nd ) nd = ++tw ;
    if( lf == rg ){
        nd->siz = 1 ;
        return ;
    }
    int mid = ( lf + rg ) >> 1 ;
    if( val <= mid ) Insert( nd->ls , lf ,  mid , val ) ;
    else             Insert( nd->rs , mid+1, rg , val ) ;
    nd->update() ;
}

long long Lcost , Rcost , ans ;

/*void print( Node *nd ){
    printf( "(%lld %lld)\n", nd->ls?nd->ls->siz:0 , nd->rs?nd->rs->siz:0 ) ; 
}*/

void Merge( Node *&nd , Node *x , Node *y , int lf , int rg ){
//  printf( "%d %d %d [%d %d]\n" , nd , x , y , lf , rg ) ;
    if( !x ){
        nd = y ;
//      printf( "[%d %d] No x at %lld nd(%d)\n" , lf , rg , y?y->siz:0 , nd ) ;
        return ;
    }
    if( !y ){
        nd = x ;
//      printf( "[%d %d] No y at %lld nd(%d)\n" , lf , rg , x->siz , nd ) ;
        return ;
    }
    if( !nd ) nd = ++tw ;
    int mid = ( lf + rg ) >> 1 ;
    if( x->rs && y->ls ) Lcost += 1LL * x->rs->siz * y->ls->siz ;
    if( y->rs && x->ls ) Rcost += 1LL * y->rs->siz * x->ls->siz ;
//  print( nd ) ;
    Merge( nd->ls , x->ls , y->ls , lf , mid ) ;
//  print( nd ) ;
//  printf( "pot (%d %d)\n" , nd->ls , nd->rs ) ;
    Merge( nd->rs , x->rs , y->rs , mid+1 , rg ) ;
//  print( nd ) ;
//  printf( "pot (%d %d)\n" , nd->ls , nd->rs ) ;
    nd->update() ;

}

/*void print( Node *nd , int lf , int rg ){
    int mid = ( lf + rg ) >> 1 ;
    if( nd->ls ) print( nd->ls , lf , mid ) ;
    printf( "%d [%d %d] siz(%lld)\n" , nd , lf , rg , nd->siz ) ;
    if( nd->rs ) print( nd->rs , mid+1, rg ) ;
}*/

void dfs( Tree *nd ){
    if( !nd || nd->val ) return ;
    dfs( nd->ls ) ; dfs( nd->rs ) ;
    Lcost = Rcost = 0 ;
//  printf( "Mer : %d %d %d\n" , nd-tc , nd->ls-tc , nd->rs-tc ) ;
/*  if( nd->ls-tc == 2 ){
        print( root[ nd->ls-tc ] , 1 , N ) ;
        print( root[ nd->rs-tc ] , 1 , N ) ;
    }
*/
    Merge( root[nd-tc] , root[ nd->ls-tc ] , root[ nd->rs-tc ] , 1 , N ) ;
//  printf( "Mer ended , siz = %lld\n" , root[nd-tc]->siz ) ;
    ans += min( Lcost , Rcost ) ;
}

void solve(){
    for( Tree *tmp = tc + 1 ; tmp <= ttc ; tmp ++ )
        if( tmp->val ) Insert( root[tmp-tc] , 1 , N , tmp->val ) ;
    dfs( treeRoot ) ;
    printf( "%lld" , ans ) ;
}

int main(){
    scanf( "%d" , &N ) ;
    readTree( treeRoot ) ;
    solve() ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值