zoukankan      html  css  js  c++  java
  • 「CF765F」Souvenirs

    题目

    点这里看题目。

    分析

    蛮巧妙的一道题目。

    首先,虽然这个问题看起来有很明显的分块特征,但是我们可以对问题进行离线,并使用常用技巧——扫描右端点,维护左端点的一些信息。直接维护答案明显过于复杂,我们可以维护每个单点的贡献,然后区间求 (min)

    具体来说,当扫描到 (r) 的时候,对于 (1le lle r),我们需要维护好 (c_l=min_{l<kle r}|a_k-a_l|),这样区间求 (min) 即可得到正确答案。

    但是,直接维护 (c_l) 还是显得过于复杂。由于在询问的时候,右端点不变,所以我们只需要保证区间求 (min) 的结果一定正确。当我们移动 (r),尝试用 (a_r) 去更新之前的 (c_l) 的时候,如果发现 (min_{l<kle r}c_k) 不大于更新过的 (c_l),那么继续更新就是没有意义的,因为此时区间求 (min) 并不会导致 ([l,r]) 的答案发生变化。

    现在具体考虑如何维护。如果我们使用线段树维护 (c) 的区间最值,那么我们应当优先更新右子树。如果右子树更新完后,右子树的最小值不劣于左子树更新过后的信息,那么此时再进入左子树继续更新就是毫无意义的,我们即可剪枝退出。为了快速算出 (a_r) 对于某个区间的贡献,我们可以在区间上维护该区间的 (a) 排好序后的序列,也就是所谓“归并树”。


    分析一下复杂度。我们关心的就是每个 (l)(c_l) 必须改变的次数,因此可以简单画图:

    souvenir.png

    上图描述了值域上的分布情况。其中 (i<j),且 (j) 是令 (|a_k-a_i|=c_i) 的最小的 (k),而黑色的线表示 (frac{a_i+a_j}{2})

    此时,如果 (a_rle frac{a_i+a_j}{2}),那么 (a_i) 一定会被更新,并且 (c_i) 会折半。另一方面,如果 (a_r>frac{a_i+a_j}{2}),则 (a_i) 不需要更新,因为根据我们的优化方法,(a_j) 的更新足以让 ([i,r]) 取到正确的答案;因此每次修改可以使 (c_i) 折半,修改次数即 (O(log a))

    总复杂度应为 (O(nlog^2nlog a))

    小结:

    1. 本题中最突出的就是剪枝技巧的运用。对于 (min,max) 这样信息的维护,我们只需要保证答案可以被找到,而不需要保证状态集合和实际计算的集合是一致的,这就可以帮助我们修改计算几何以简化运算。
    2. 可以学习一下复杂度分析的方法。

    代码

    #include <cstdio>
    #include <vector>
    #include <algorithm>
    
    #define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
    #define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
    
    const int INF = 0x3f3f3f3f;
    const int MAXN = 3e5 + 5;
    
    template<typename _T>
    void read( _T &x )
    {
    	x = 0; char s = getchar(); bool f = false;
    	while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
    	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
    	if( f ) x = -x;
    }
    
    template<typename _T>
    void write( _T x )
    {
    	if( x < 0 ) putchar( '-' ), x = -x;
    	if( 9 < x ) write( x / 10 );
    	putchar( x % 10 + '0' );
    }
    
    template<typename _T>
    _T MIN( const _T a, const _T b )
    {
    	return a < b ? a : b;
    }
    
    std :: vector<int> que[MAXN];
    std :: vector<int> seq[MAXN << 2];
    
    int mn[MAXN << 2], cur;
    
    int qL[MAXN], ans[MAXN];
    
    int A[MAXN];
    int N, M;
    
    inline void Upt( const int x ) { mn[x] = MIN( mn[x << 1], mn[x << 1 | 1] ); }
    
    void Build( const int x, const int l, const int r )
    {
    	if( l > r ) return ; mn[x] = INF;
    	if( l == r ) { seq[x].push_back( A[l] ); return ; }
    	int mid = ( l + r ) >> 1;
    	Build( x << 1, l, mid );
    	Build( x << 1 | 1, mid + 1, r );
    	seq[x].resize( r - l + 1 );
    	std :: merge( seq[x << 1].begin(), seq[x << 1].end(),
    				  seq[x << 1 | 1].begin(), seq[x << 1 | 1].end(),
    				  seq[x].begin() );
    }
    
    void Update( const int x, const int l, const int r, const int segL, const int segR, const int nVal )
    {
    	if( segL > segR ) return ;
    	if( segL <= l && r <= segR )
    	{
    		std :: vector<int> :: iterator it = std :: upper_bound( seq[x].begin(), seq[x].end(), nVal );
    		if( it != seq[x].end() ) mn[x] = MIN( mn[x], *it - nVal );
    		if( it != seq[x].begin() ) mn[x] = MIN( mn[x], nVal - *( -- it ) );
    		if( cur <= mn[x] ) return ;
    	}
    	if( l == r ) return ( void ) ( cur = MIN( cur, mn[x] ) );
    	int mid = ( l + r ) >> 1;
    	if( mid  < segR ) Update( x << 1 | 1, mid + 1, r, segL, segR, nVal );
    	if( segL <= mid ) Update( x << 1, l, mid, segL, segR, nVal );
    	Upt( x ), cur = MIN( cur, mn[x] );
    }
    
    int Query( const int x, const int l, const int r, const int segL, const int segR )
    {
    	if( segL <= l && r <= segR ) return mn[x];
    	int mid = ( l + r ) >> 1, ret = INF;
    	if( segL <= mid ) ret = MIN( ret, Query( x << 1, l, mid, segL, segR ) );
    	if( mid  < segR ) ret = MIN( ret, Query( x << 1 | 1, mid + 1, r, segL, segR ) );
    	return ret;
    }
    
    int main()
    {
    	read( N );
    	rep( i, 1, N ) read( A[i] );
    	read( M );
    	rep( i, 1, M )
    	{
    		int r;
    		read( qL[i] ), read( r );
    		que[r].push_back( i );
    	}
    	Build( 1, 1, N );
    	rep( i, 1, N )
    	{
    		cur = INF, Update( 1, 1, N, 1, i - 1, A[i] );
    		for( int j = 0 ; j < ( int ) que[i].size() ; j ++ )
    			ans[que[i][j]] = Query( 1, 1, N, qL[que[i][j]], i );
    	}
    	rep( i, 1, M ) write( ans[i] ), putchar( '
    ' );
    	return 0;
    }
    
    
  • 相关阅读:
    Dynamics CRM9.0更新了Chrome后菜单按钮变形
    质量属性
    机器学习理解
    大道之简的理解
    架构之美理解
    1.13
    1.12
    1.11
    1.10
    Flex 替换输入的字符串
  • 原文地址:https://www.cnblogs.com/crashed/p/15412817.html
Copyright © 2011-2022 走看看