「雅礼集训 2017 Day1」市场
题目
点这里看题目。
分析
看起来其它操作都易于维护,唯独这个区间下取整除的修改非常不可做。唯一的办法似乎是暴力地修改每一个节点。
但是我们可以挑出一些特殊情况来特殊处理。比如,考虑一个最大值为 \(a\),最小值为 \(b\) 的区间。如果对于 \(d\) 求下取整除的时候,出现了 \(\lfloor\frac a d\rfloor=\lfloor\frac b d\rfloor\),那么这个时候修改相当于区间赋值,可以打标记;如果出现了 \(a-\lfloor\frac a d\rfloor=b-\lfloor\frac b d\rfloor\),那么就相当于区间加,可以打标记。
然后加上两个剪枝......就可以通过了?!看起来跑得很快,下面我们需要证明复杂度。
先来考虑两种特判。对于 \(\lfloor\frac a d\rfloor=\lfloor\frac b d\rfloor\),它的一条充分条件为 \(a=b\);而对于 \(a-\lfloor\frac a d\rfloor=b-\lfloor\frac b d\rfloor\),如果此时有 \(\lfloor\frac a d\rfloor\not=\lfloor\frac b d\rfloor\),那么一定会有 \(\lfloor\frac a d\rfloor=\lfloor\frac b d\rfloor+1\) 且 \(a=b+1\)。
因此,对于 \(a-b\le 1\) 的区间,由于可以直接标记处理,因此它不会贡献暴力修改的复杂度。而如果有 \(a-b=c\ge 2\),那么可以得到 \(\lfloor\frac a d\rfloor-\lfloor\frac b d\rfloor\le \lceil\frac c d\rceil\),这个区间最多被暴力修改 \(O(\log c)\) 次。因此,设某个结点 \(x\) 的区间内最大值为 \(a_x\),最小值为 \(b_x\),则它的势能应该被定义为 \(\phi(x)=\log(\max\{1,a_x-b_x\})\),整棵树的势能为 \(\Phi=\sum_x\phi(x)=\sum_x\log(\max\{1,a_x-b_x\})\)。
通过势能来分析复杂度:
-
初始时:势能为 \(O(n\log|V|)\),其中 \(V\) 为值域。
-
区间加:对于那些被完全覆盖的结点,势能不变。而对于那些仅和修改区间有交、但没被覆盖的结点,由线段树的复杂度分析可知,这些结点数量为 \(O(\log n)\),每个结点的势能变化量为 \(O(\log |C|)\),其中 \(C\) 为修改的值的值域。
这部分的 \(\Delta_{\Phi}=O(\log n\log |C|)\)。
-
区间下取整除:这一部分有基础复杂度 \(O(\log n)\),属于没有被区间完全覆盖的那些结点的遍历复杂度。
而考虑一个被暴力修改的结点 \(x\),一旦它被包含、且递归进入修改,那么 \(\Delta_{\phi(x)}\le -1\)。由于势能不会超过 \(O(n\log|V|+q\log n\log|C|)\),因此这样的递归修改只会进行不超过 \(O(n\log|V|+q\log n\log|C|)\) 次,单次修改 \(O(1)\)。
因此,该算法的复杂度为 \(O(q\log n\log|C|+n\log |V|)\)。
小结:
- 特别注意势能分析的关键:定义势函数和分析势能变化。势函数的定义需要关注算法中决定复杂度的关键量,而分析势能变化则需要分析哪些应该被放入势能,而哪些应该被直接算入复杂度;
- 特别注意势能线段树的实现:关注一下分析的边界情况,比如本题中,如果不处理 \(a-b=1\) 的情况,那么会出现 \(\lceil\frac{1}{d}\rceil=1\) 的情况,导致势能没法下降;此外,如果不能得到较好的分析,那么在保证正确的前提下可以尽量多加一点优化。
代码
#include <cmath>
#include <cstdio>
#include <iostream>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
typedef long long LL;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e5 + 5;
template<typename _T>
void read( _T &x )
{
x = 0; char s = getchar(); int f = 1;
while( ! ( '0' <= s && s <= '9' ) ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
LL su[MAXN << 2];
int mx[MAXN << 2], mn[MAXN << 2];
int tagCov[MAXN << 2], tagAdd[MAXN << 2];
int A[MAXN];
int N, Q;
inline int Div( const int a, const int b ) { return ( int ) floor( 1.0 * a / b ); }
inline void Upt( const int x )
{
su[x] = su[x << 1] + su[x << 1 | 1];
mx[x] = std :: max( mx[x << 1], mx[x << 1 | 1] );
mn[x] = std :: min( mn[x << 1], mn[x << 1 | 1] );
}
inline void Add( const int x, const int l, const int r, const int delt )
{
su[x] += 1ll * ( r - l + 1 ) * delt;
mx[x] += delt, mn[x] += delt, tagAdd[x] += delt;
}
inline void Cover( const int x, const int l, const int r, const int v )
{
su[x] = 1ll * ( r - l + 1 ) * v;
mx[x] = mn[x] = tagCov[x] = v, tagAdd[x] = 0;
}
inline void Normalize( const int x, const int l, const int r )
{
int mid = ( l + r ) >> 1;
if( tagCov[x] != - INF )
{
Cover( x << 1, l, mid, tagCov[x] );
Cover( x << 1 | 1, mid + 1, r, tagCov[x] );
tagCov[x] = - INF;
}
if( tagAdd[x] )
{
Add( x << 1, l, mid, tagAdd[x] );
Add( x << 1 | 1, mid + 1, r, tagAdd[x] );
tagAdd[x] = 0;
}
}
void Build( const int x, const int l, const int r )
{
if( l > r ) return ;
tagCov[x] = - INF, tagAdd[x] = 0;
if( l == r ) { su[x] = mx[x] = mn[x] = A[l]; return ; }
int mid = ( l + r ) >> 1;
Build( x << 1, l, mid );
Build( x << 1 | 1, mid + 1, r );
Upt( x );
}
void OrderAdd( const int x, const int l, const int r, const int segL, const int segR, const int delt )
{
if( segL <= l && r <= segR ) { Add( x, l, r, delt ); return ; }
int mid = ( l + r ) >> 1; Normalize( x, l, r );
if( segL <= mid ) OrderAdd( x << 1, l, mid, segL, segR, delt );
if( mid < segR ) OrderAdd( x << 1 | 1, mid + 1, r, segL, segR, delt );
Upt( x );
}
void Div( const int x, const int l, const int r, const int d )
{
int t1 = Div( mx[x], d ), t2 = Div( mn[x], d );
if( t1 == t2 ) { Cover( x, l, r, t1 ); return ; }
if( t1 - mx[x] == t2 - mn[x] ) { Add( x, l, r, t1 - mx[x] ); return ; }
if( l == r )
{
su[x] = mx[x] = mn[x] = Div( mx[x], d );
return ;
}
int mid = ( l + r ) >> 1; Normalize( x, l, r );
Div( x << 1, l, mid, d );
Div( x << 1 | 1, mid + 1, r, d );
Upt( x );
}
void OrderDiv( const int x, const int l, const int r, const int segL, const int segR, const int d )
{
if( segL <= l && r <= segR ) { Div( x, l, r, d ); return ; }
int mid = ( l + r ) >> 1; Normalize( x, l, r );
if( segL <= mid ) OrderDiv( x << 1, l, mid, segL, segR, d );
if( mid < segR ) OrderDiv( x << 1 | 1, mid + 1, r, segL, segR, d );
Upt( x );
}
LL QuerySum( const int x, const int l, const int r, const int segL, const int segR )
{
if( segL <= l && r <= segR ) return su[x];
int mid = ( l + r ) >> 1; LL ret = 0; Normalize( x, l, r );
if( segL <= mid ) ret += QuerySum( x << 1, l, mid, segL, segR );
if( mid < segR ) ret += QuerySum( x << 1 | 1, mid + 1, r, segL, segR );
return ret;
}
int QueryMin( 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; Normalize( x, l, r );
if( segL <= mid ) ret = std :: min( ret, QueryMin( x << 1, l, mid, segL, segR ) );
if( mid < segR ) ret = std :: min( ret, QueryMin( x << 1 | 1, mid + 1, r, segL, segR ) );
return ret;
}
int main()
{
read( N ), read( Q );
rep( i, 1, N ) read( A[i] );
Build( 1, 1, N );
int opt, A, B, C;
while( Q -- )
{
read( opt ), read( A ), read( B ), A ++, B ++;
if( opt == 1 ) read( C ),
OrderAdd( 1, 1, N, A, B, C );
if( opt == 2 ) read( C ),
OrderDiv( 1, 1, N, A, B, C );
if( opt == 3 ) write( QueryMin( 1, 1, N, A, B ) ), putchar( '\n' );
if( opt == 4 ) write( QuerySum( 1, 1, N, A, B ) ), putchar( '\n' );
}
return 0;
}