zoukankan      html  css  js  c++  java
  • 题解——Dynamic Rankings(树状数组+主席树)

    题解——Dynamic Rankings(树状数组+主席树)

    这道题可以整体二分过,但我写了树套树
    而且这个外层树不是那么明显


    题目传送门

    给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]的值,改变后,程序还能针对改变后的a继续回答上面的问题。你需要编一个这样的程序,从输入文件中读入序列a,然后读入一系列的指令,包括询问指令和修改指令。

    第一行有两个正整数n(1≤n≤100000),m(1≤m≤100000)。分别表示序列的长度和指令的个数。

    第二行有n个数,表示a[1],a[2]……a[n],这些数都小于10^9。接下来的m行描述每条指令,每行的格式是下面两种格式中的一种。 Q i j k 或者 C i t

    Q i j k (i,j,k是数字,1≤i≤j≤n, 1≤k≤j-i+1)表示询问指令,询问a[i],a[i+1]……a[j]中第k小的数。

    C i t (1≤i≤n,0≤t≤10^9)表示把a[i]改变成为t。

    简化题意

    带单点修改的区间第K小(大)

    关于静态第K大(主席树)

    默认各位是会主席树的,只是复习一下,具体讲解在代码中
    就是对原数组离散化,每个状态为一颗新的权值线段树,维护一个类似于前缀和的东西,由于每次相同部分很多,考虑用可持久化线段树节约空间(每次只开一条链)。

    AC code(静态):

    #include<bits/stdc++.h>
    using namespace std;
    const  int  MAXN = 1000005 ;  
    inline int read(){
        int s = 0,w = 1;char g = getchar();while(g<'0'||g>'9'){if(g=='-')w*=-1;g = getchar();}
        while(g>='0'&&g<='9'){s = s*10+g-'0';g = getchar();}return s*w;
    }
    int  b[ MAXN ] , a [ MAXN ] , root[ MAXN ] ;
    int  N , M , cnt = 0 ;
    struct Segment{
    	int  ls , rs , num ;
    }t[ MAXN<<5 ] ; 
    void copyNode( int x , int y ){
    	t[ x ].ls = t[ y ].ls , t[ x ].rs = t[ y ].rs , t[ x ].num = t[ y ].num+1 ;
    }
    void build( int p , int  l , int  r ){//初始建树
    	t[ p ].num = 0 ;
    	if( l == r )return ;
    	int  mid = ( l+r )>>1 ;
    	build( t[ p ].ls = ++cnt , l , mid ) ;
    	build( t[ p ].rs = ++cnt , mid+1 , r ) ;
    }
    int New_made( int las , int  l , int r , int pos ){//新建,las为上一状态
    	int p = ++cnt ; 
    	copyNode( p , las ) ;//新建的链深=树深,别想多了 
    	if( l == r )return p ;
    	int mid = ( l+r )>> 1 ; 
    	if( pos <= mid )t[ p ].ls = New_made( t[ p ].ls , l , mid , pos ) ;//二分查找
    	else t[ p ].rs = New_made( t[ p ].rs , mid+1 , r , pos ) ;
    	return p ; 
    }
    int  ask( int x , int  y , int  l , int  r , int rank ){
    	if( l == r )return l ;
    	int  mid = (l+r)>>1 ;
    	int  dif = t[ t[x].ls ].num - t[ t[y].ls ].num ;
    	if( rank <= dif )ask( t[ x ].ls , t[ y ].ls , l , mid , rank ) ;//值域上二分
    	else  ask( t[ x ].rs , t[ y ].rs , mid+1 , r , rank-dif ) ;
    }
    int main(){
    	N = read() , M = read() ;//平时不用register了 
    	for( int i = 1 ; i <= N ; ++i )b[ i ] = a[ i ] = read() ; 
    	sort( b+1 , b+N+1 ) ;
    	int  tot = unique( b + 1 , b + 1 + N ) - b - 1 ;//自带的去重 ,返回的是数组的尾地址
    	build( root[0] = ++cnt , 1 , tot ) ; //root , l , r
    	for( int i = 1 ; i <= N ; ++i ){//nlogn,等效Hash
    		int pos = lower_bound( b+1 , b+tot+1 , a[ i ] ) - b ; 
    		root[ i ] = New_made( root[i-1] , 1 , tot , pos ) ; // 上一版本root , l , r , pos  
    	}
    	for( int i = 1 ; i <= M ; ++i ){
    		int m1 = read() , m2 = read() , m3 = read() ;
    		int t = ask( root[ m2 ] , root[ m1-1 ] , 1 , tot , m3 ) ; //前缀和思想 
    		printf("%d
    ",b[ t ] ) ;
    	}
    	return 0 ;
    }
    
    

    关于动态第K大(树状数组套主席树)

    上面每棵树都是一个历史状态,即第i棵树代表了第i个状态时序列 1~i 所对应的值域线段树树上形态,就是维护了一个前缀和。

    然后,带修改,如果单纯的修改前缀包含该位置的所有树,明显T飞的。

    然后,某些神仙们并不满足,既然每棵树和序列上的一个前缀区间可以相互对应,那为何不能用树状数组像维护 n 个数的序列一样维护 n 棵主席树呢?(请回顾下图)

    上图中的 c[1]~c[8] 都会对应一颗主席树。然后,每次修改是 log 棵数一起修改 , 每次查询仍然是两个状态, 但每个状态都会由 log 棵树一起组成 。 所以.....时间和空间复杂度都是 NlogN^2 。

    具体的我写在代码中了(参考来参考去,最后终于写了一个好点的板子)

    #include<bits/stdc++.h>
    using namespace std ;
    const  int  MAXN = 100005 ;//log 16
    inline int read(){
        int s = 0,w = 1;char g = getchar();while(g<'0'||g>'9'){if(g=='-')w*=-1;g = getchar();}
        while(g>='0'&&g<='9'){s = s*10+g-'0';g = getchar();}return s*w;
    }
    int  b[ MAXN*2 ] , a[ MAXN ] , root[ MAXN*18 ] ;
    int  N , M  , cnt = 0 , totx = 0 , toty = 0  , xx[ 18 ] , yy[ 18 ] , tot = 0 ;
    int  opa[ MAXN ] , opb[ MAXN ] , opc[ MAXN ] ;
    char  op[ 20 ] ; 
    struct  Segment{
    	int ls , rs , num ;
    }t[ MAXN*324 ] ; // 空间log^2 
    void copyNode( int x , int y , int z ){
    	if( x == 0 )return ; 
    	t[ x ].ls = t[ y ].ls , t[ x ].rs = t[ y ].rs , t[ x ].num = t[ y ].num + z ;
    }
    int New_made( int las , int  l , int  r , int  pos , int val ){
    	int  p = ++cnt  ;
    	copyNode( p , las , val ) ;// las = 0 一样 
    	if( l == r )return p ;
    	int  mid = (l+r)>>1 ;
    	if( pos <= mid )t[ p ].ls = New_made( t[ p ].ls , l , mid , pos , val ) ;
    	else t[ p ].rs = New_made( t[ p ].rs , mid+1 , r , pos , val ) ;
    	return p ;
    }
    int  query( int  l , int  r , int k ){//实现log棵树同时移动 
    	if( l == r ) return l;
        int sum = 0 , mid = (l+r)/2; // sum是 log个 状态差 的和
        for( int i=1;i<=totx;i++) sum += t[t[ xx[i] ].ls].num ;
        for( int i=1;i<=toty;i++) sum -= t[t[ yy[i] ].ls].num ;
        if( sum >= k ){//二分,log棵树同时向下进入左区间或右区间,请读者自行脑补
            for(int i=1;i<=totx;i++) xx[i]=t[ xx[i] ].ls;
            for(int i=1;i<=toty;i++) yy[i]=t[ yy[i] ].ls;
            return query( l , mid , k ) ;
        }else{
            for(int i=1;i<=totx;i++) xx[i]=t[ xx[i] ].rs;
            for(int i=1;i<=toty;i++) yy[i]=t[ yy[i] ].rs;
            return query( mid+1 , r , k-sum ) ;
        }
    }
    void add( int x , int val ){//log次增加,就是多开了log条链
    	int pos = lower_bound( b+1 , b+tot+1 , a[ x ] ) - b ; 
    	for( int i = x ; i <= N ; i += i&-i )
    	root[ i ] = New_made( root[ i ] , 1 , tot , pos , val ) ; // 上一版本root , l , r , pos  
    }
    int  ask( int x , int  y , int  z ){// 找出每次修改对应的log棵树
    	totx = toty = 0 ;
        for( int j = y ; j ; j -=j&(-j) ) xx[++totx]=root[j];
        for( int j = x - 1 ; j ; j -=j&(-j) ) yy[++toty]=root[j];
        int ans = query( 1 , tot , z ) ;
        return b[ans] ;
    }
    int main(){
    	N = read() , M = read() , tot = N ;
    	for( int i = 1 ; i <= N ; ++i )b[ i ] = a[ i ] = read() ;
    	for( int i = 1 ; i <= M ; ++i ){
    		scanf("%s",op);
    		opa[ i ] = read() , opb[ i ] = read() ;//离线后,方便进行离散化
    		if( op[ 0 ]=='Q' )opc[ i ] = read() ;
    		else b[ ++tot ] = opb[ i ] ; 
    	}
    	sort( b+1 , b+tot+1 ) ; 
    	tot = unique( b+1 , b+tot+1 )-b-1 ; //离散化,同静态
    	for( int i = 1 ; i <= N ; ++i )add( i , 1 ) ;
    	for( int i = 1 ; i <= M ; ++i )
    		if( opc[ i ] )printf("%d
    ",ask( opa[ i ] , opb[ i ] , opc[ i ]) ) ;
    		else {
    			add( opa[ i ] , -1 ) ;//修改,树上对应的个数 -1 
    			a[ opa[ i ] ] = opb[ i ] ;//序列上对应修改
    			add( opa[ i ] , 1 ) ;//个数+1
    		}
    	return 0 ;
    }
    

    然后,记得树的空间要开log^2倍,别像ssw02一样挂了。

    本文的不足 ,还请各位大佬指出,ssw02感谢您的阅读。

  • 相关阅读:
    菜鸟学习Spring Web MVC之二
    菜鸟学习Spring Web MVC之一
    Internet Explorer 6 的15个讨厌的bug和简单的解决方法
    前端遇到的跨域问题及解决方案二
    前端遇到的跨域问题及解决方案一
    第六 添加文字
    第五章、使用预绘制图片
    第四、渐变和图案
    第三 画曲线
    第二、画线和路径
  • 原文地址:https://www.cnblogs.com/ssw02/p/11248065.html
Copyright © 2011-2022 走看看