题目
点这里看题目。
分析
看起来其它操作都易于维护,唯独这个区间下取整除的修改非常不可做。唯一的办法似乎是暴力地修改每一个节点。
但是我们可以挑出一些特殊情况来特殊处理。比如,考虑一个最大值为 (a),最小值为 (b) 的区间。如果对于 (d) 求下取整除的时候,出现了 (lfloorfrac a d floor=lfloorfrac b d floor),那么这个时候修改相当于区间赋值,可以打标记;如果出现了 (a-lfloorfrac a d floor=b-lfloorfrac b d floor),那么就相当于区间加,可以打标记。
然后加上两个剪枝......就可以通过了?!看起来跑得很快,下面我们需要证明复杂度。
先来考虑两种特判。对于 (lfloorfrac a d floor=lfloorfrac b d floor),它的一条充分条件为 (a=b);而对于 (a-lfloorfrac a d floor=b-lfloorfrac b d floor),如果此时有 (lfloorfrac a d floor ot=lfloorfrac b d floor),那么一定会有 (lfloorfrac a d floor=lfloorfrac b d floor+1) 且 (a=b+1)。
因此,对于 (a-ble 1) 的区间,由于可以直接标记处理,因此它不会贡献暴力修改的复杂度。而如果有 (a-b=cge 2),那么可以得到 (lfloorfrac a d floor-lfloorfrac b d floorle lceilfrac c d ceil),这个区间最多被暴力修改 (O(log c)) 次。因此,设某个结点 (x) 的区间内最大值为 (a_x),最小值为 (b_x),则它的势能应该被定义为 (phi(x)=log(max{1,a_x-b_x})),整棵树的势能为 (Phi=sum_xphi(x)=sum_xlog(max{1,a_x-b_x}))。
通过势能来分析复杂度:
-
初始时:势能为 (O(nlog|V|)),其中 (V) 为值域。
-
区间加:对于那些被完全覆盖的结点,势能不变。而对于那些仅和修改区间有交、但没被覆盖的结点,由线段树的复杂度分析可知,这些结点数量为 (O(log n)),每个结点的势能变化量为 (O(log |C|)),其中 (C) 为修改的值的值域。
这部分的 (Delta_{Phi}=O(log nlog |C|))。
-
区间下取整除:这一部分有基础复杂度 (O(log n)),属于没有被区间完全覆盖的那些结点的遍历复杂度。
而考虑一个被暴力修改的结点 (x),一旦它被包含、且递归进入修改,那么 (Delta_{phi(x)}<0)。由于势能不会超过 (O(nlog|V|+qlog nlog|C|)),因此这样的递归修改只会进行不超过 (O(nlog|V|+qlog nlog|C|)) 次,单次修改 (O(1))。
因此,该算法的复杂度为 (O(qlog nlog|C|+nlog |V|))。
小结:
- 特别注意势能分析的关键:定义势函数和分析势能变化。势函数的定义需要关注算法中决定复杂度的关键量,而分析势能变化则需要分析哪些应该被放入势能,而哪些应该被直接算入复杂度;
- 特别注意势能线段树的实现:关注一下分析的边界情况,比如本题中,如果不处理 (a-b=1) 的情况,那么会出现 (lceilfrac{1}{d} ceil=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( '
' );
if( opt == 4 ) write( QuerySum( 1, 1, N, A, B ) ), putchar( '
' );
}
return 0;
}