Loading

[FJOI2018]领导集团问题

\(link\)
这道题,是一个线段树合并优化\(dp\)的题.
我们先想朴素的\(dp\)式子
\(f [i][j]\)表示以\(i\)为根节点,最小值为\(j\)的时候最大部门子集,那么考虑对于任意一个状态,我们有两种情况:选择根节点和不选择根节点
由于这道题的状态表示,所以对于每个状态,他所选择的数一定在他的子节点内
如果不选择根节点,\(f [i][j] = \sum_{y \in \ I's \ son}f [y][j]\)
如果选择根节点,那么\(f [i][j] = f [i][c [x].rk] + 1\)
对两者取\(max\)即可
本人表述能力有限,直接看代码比较好

inline void dfs( int x ){
  for( R i = head [x] ; ~i ; i = a [i].next ){
    int y = a [i].to ;  
    dfs( y ) ;
    for( R j = 1 ; j <= knt ; j ++ )
      f [x][j] += f [y][j] ;
  } 
  for( R i = 1 ; i <= c [x].rk ; i ++ )
    f [x][i] = max( f [x][i] , f [x][c [x].rk] + 1 ) ;
} 

我们在考虑对这个代码进行优化,很显然\(dfs\)并不能优化掉,这里面主要费时间的就是一个区间加区间和一个区间取\(max\)
这个时候我们就可以对区间加法进行标记下传,对区间去\(max\)进行标记永久化,这样实现难度和码量都会降低(因为\(max\)打永久化是真的香)
细节见代码注释

//对加法标记进行释放,一路上的([max标记的]data)的max就是最终查询的答案
#include <cstring>
#include <cstdio>
#include <algorithm>			
#define R register int
#define printf tz1 = printf

using namespace std ;
const int N = 2e5 + 10 ;
typedef long long L ;
typedef double D ;

inline int read(){
	int w = 0 ; bool fg = 0 ; char ch = getchar() ;
	while( ch > '9' || ch < '0' ) fg |= ( ch == '-' ) , ch = getchar() ;
	while( ch >= '0' && ch <= '9' ) w = ( w << 1 ) + ( w << 3 ) + ( ch - '0' ) , ch = getchar() ;
	return fg ? -w : w ; 
}

int tz1 , n , cnt , gnt , knt , head [N] , root [N] , p ;
struct S{ int w , index , rk ; } c [N] ;
struct E{ int fr , to , next ; } a [N] ;
struct T{ int l , r , ls , rs , data , lz ; } t [N << 6] ;

inline bool cmp( S a , S b ){ return a.w < b.w ; }
inline bool csp( S a , S b ){ return a.index < b.index ; }

inline void add( int f , int t ){
	a [++ cnt].fr = f ;
	a [cnt].to = t ;
	a [cnt].next = head [f] ;
	head [f] = cnt ;
}

inline void sp( int x ){ //下传加法标记
	if( ! t [x].lz ) return ;
	if( t [x].ls ) t [t [x].ls].data += t [x].lz , t [t [x].ls].lz += t [x].lz ;
	if( t [x].rs ) t [t [x].rs].data += t [x].lz , t [t [x].rs].lz += t [x].lz ;
	t [x].lz = 0 ;
}

inline int ask( int x , int k ){ //单点查询:对加法标记进行释放,对max标记一路取max进行处理
	if( !x ) return 0 ; 
	if( t [x].l == t [x].r ) return t [x].data ;
	sp( x ) ; 
	int mid = ( t [x].l + t [x].r ) >> 1 ;
	if( k <= mid ) return max( t [x].data , ask( t [x].ls , k ) ) ;
	else return max( t [x].data , ask( t [x].rs , k ) ) ; 
} 

inline void cge( int &x , int Ls , int Rs , int l , int r , int k ){ //区间取max,直接标记永久化
	if( !x ) x = ++ gnt , t [x].l = Ls , t [x].r = Rs ;
//	printf( "CGE%d %d %d %d %d\n" , x , t [x].l , t [x].r , l , r ) ;
	if( t [x].l >= l && t [x].r <= r ){ t [x].data = max( t [x].data , k ) ; return ; } // 这里实现标记,其实是将标记打在数据上,这就是给max打永久标记的精髓和优势 
	sp( x ) ;
	int mid = ( t [x].l + t [x].r ) >> 1 ;
	if( l <= mid ) cge( t [x].ls , Ls , mid , l , r , k ) ;
	if( r > mid ) cge( t [x].rs , mid + 1 , Rs , l , r , k ) ;
}

inline int mer( int x , int y , int lax , int rax ){ //线段树合并实现区间加法
	lax = max( lax , t [x].data ) , rax = max( rax , t [y].data ) ;
	if( !x || ! y ){
		if( x ) t [x].data += rax , t [x].lz += rax ; //这个格外注意,防止两个都是0对0号节点操作导致错误
		if( y ) t [y].data += lax , t [y].lz += lax ;
		return x + y ;	
	}  sp ( x ) , sp ( y ) ;
	t [x].ls = mer( t [x].ls , t [y].ls , lax , rax ) ;
	t [x].rs = mer( t [x].rs , t [y].rs , lax , rax ) ;
	t [x].data = rax + lax ; return x ;
}

inline void dfs( int x ){
//	printf( "DFS%d\n"  , x ) ;
	for( R i = head [x] ; ~i ; i = a [i].next ){
		int y = a [i].to ;
		dfs( y ) ;
		root [x] = root [y] = mer( root [x] , root [y] , 0 , 0 ) ; 
	} if( c [x].rk >= 1 )  
	cge( root [x] , 1 , knt , 1 , c [x].rk , ask( root [x] , c [x].rk ) + 1 ) ;
}

void sc(){
	n = read() ; memset( head , -1 , sizeof( head ) ) ;
	for( R i = 1 ; i <= n ; i ++ ) c [i].w = read() , c [i].index = i ;
	for( R i = 2 ; i <= n ; i ++ ) p = read() , add( p , i ) ;
	
	sort( c + 1 , c + 1 + n , cmp ) ;
	
	c [0].w = -0x7fffffff ;
	
	for( R i = 1 ; i <= n ; i ++ ) if( c [i].w == c [i - 1].w ) c [i].rk = knt ; else c [i].rk = ++ knt ;
	sort( c + 1 , c + 1 + n , csp ) ;
}

void work(){
	dfs( 1 ) ;
	printf( "%d\n" , ask( root [1] , 1 ) ) ;
}

signed main(){
	sc() ;
	work() ;
	return 0 ; 
}

posted @ 2021-05-20 17:31  Soresen  阅读(114)  评论(1)    收藏  举报