题目
点这里看题目。
分析
蛮巧妙的一道题目。
首先,虽然这个问题看起来有很明显的分块特征,但是我们可以对问题进行离线,并使用常用技巧——扫描右端点,维护左端点的一些信息。直接维护答案明显过于复杂,我们可以维护每个单点的贡献,然后区间求 (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) 必须改变的次数,因此可以简单画图:
上图描述了值域上的分布情况。其中 (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))。
小结:
- 本题中最突出的就是剪枝技巧的运用。对于 (min,max) 这样信息的维护,我们只需要保证答案可以被找到,而不需要保证状态集合和实际计算的集合是一致的,这就可以帮助我们修改计算几何以简化运算。
- 可以学习一下复杂度分析的方法。
代码
#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;
}