「LOJ3673」简单数据结构
题目
点这里看题目。
给定一个长度为 \(n\) 的非负整数序列 \(a\),有 \(q\) 次操作,每次操作类型为如下三种之一:
- 给定 \(v\),表示 \(\forall 1\le i\le n\),令 \(a_i\gets \min\{a_i,v\}\)。
- 表示 \(\forall 1\le i\le n\),令 \(a_i\gets a_i+i\)。
- 给定 \(l,r\),表示你需要输出 \(\sum_{i=l}^ra_i\)。
所有数据满足 \(1\le n,q\le 2\times 10^5,0\le a_i,v\le 10^{12},1\le l\le r\le n\)。
分析
把玩一下这个问题,可以发现在 \(a\) 单调不降的时候,答案极其容易维护,因为 \(a\) 单调不降的性质在整个过程中不会受影响。具体的维护过程为:
- 所有的数都是 \(k(x-x_0)+y_0\) 的形式。
- 处理操作一:二分一个后缀,有且仅有该后缀内的数会被修改。之后将它们全部更新即可。这一部分可以结合势能用一个栈维护,这样可以将复杂度降到 \(O((n+q)\log n)\)。
- 处理操作二:自然而然。
- 处理操作三:用线段树或者啥玩意儿维护一下前缀和/区间和即可。
但是初始的 \(a\) 并不一定就是单调不降的。我们很容易想到,操作中一旦出现 \(a_i\le a_{i+1}\),我们就可以将两个数合并,之后一起解决。如果 \(a_i>a_{i+1}\),则当 \(v\le a_{i+1}\) 时,我们就可以认为两个数在操作后必然满足 \(a_{i}\le a_{i+1}\),这是一个简单的判据。
Remark.
在搞数据结构的时候,对情况的划分不一定要完全准确,但一定要方便简单。
不幸的是,这样的划分并不能明显地改善复杂度。看来我们只能激进一点了——假设 \(a\) 本身就是单调不降的,然后先算一下答案,最后进行修正。一个简单的模型为,令 \(b_i=\max_{1\le j\le i}a_j\),然后用 \(b\) 替换 \(a\)。
回头看看之前的判据,我们发现它惊人的适用:如果某操作一满足 \(v\le a_i\),则在此之后都有 \(b_i=a_i\)(这是因为若 \(x\le y\),则进行完全一样的操作后有 \(x'\le y'\))。取第一次 \(v\le a_i\) 的操作为分界,在此之前都形如 \(kx+a_i\),容易维护;在此之后可以直接用单调不降的 \(b_i\) 替换 \(a_i\),也可以维护。不过,我们需要动态“加入”或“删除”一些数,最好使用线段树维护。
所以,如果找出了分界点,之后就可以做到 \(O((n+q)\log n)\)。
找分界点很容易想到二分。如果设前 \(i\) 次操作的操作二个数为 \(p_i\),则对于 \(a_x\) 而言,判断 \(r\) 的标准为 “是否存在 \(1\le i\le r\),使得 \(v_i-p_ix\le a_x\)?”。结构为一次式求 \(\min\),可以整体二分+凸包在 \(O((n+q)\log n)\) 的时间内解决。
所以,我们可以在 \(O((n+q)\log n)\) 的时间内解决本题。
代码
#include <cstdio>
#include <vector>
#include <utility>
#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 LL INF = 1e18;
const int MAXN = 2e5 + 5;
template<typename _T>
inline void Read( _T &x ) {
x = 0; char s = getchar(); bool f = false;
while( ! ( '0' <= s && s <= '9' ) ) { 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>
inline void Write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) Write( x / 10 );
putchar( x % 10 + '0' );
}
template<typename _T>
inline _T Max( const _T &a, const _T &b ) {
return a > b ? a : b;
}
template<typename _T>
inline _T Min( const _T &a, const _T &b ) {
return a < b ? a : b;
}
std :: vector<int> chg[MAXN];
int seq[MAXN], tot;
int opt[MAXN], pref[MAXN];
LL arg1[MAXN], arg2[MAXN], ans[MAXN];
LL A[MAXN], B[MAXN];
int N, Q;
namespace FindChange {
struct Vector {
LL x, y;
Vector(): x( 0 ), y( 0 ) {}
Vector( LL X, LL Y ): x( X ), y( Y ) {}
inline Vector operator - ( const Vector &q ) const { return Vector( x - q.x, y - q.y ); }
inline Vector operator + ( const Vector &q ) const { return Vector( x + q.x, y + q.y ); }
};
Vector stk[MAXN];
int ord[MAXN], tmp1[MAXN], tmp2[MAXN];
inline LL Cross( const Vector &u, const Vector &v ) {
return u.x * v.y - u.y * v.x;
}
void Divide( const int &tL, const int &tR, const int &qL, const int &qR ) {
if( tL > tR || qL > qR ) return ;
if( tL == tR ) {
if( tL > tot ) return ;
rep( i, qL, qR )
chg[seq[tL]].push_back( ord[i] );
return ;
}
int h = 1, t = 0;
int mid = ( tL + tR ) >> 1, tot1 = 0, tot2 = 0;
for( int l = tL, r ; l <= mid ; l = r ) {
for( r = l ; r <= mid && pref[seq[r]] == pref[seq[l]] ; r ++ );
int idx = l;
rep( k, l + 1, r - 1 )
if( arg1[seq[k]] < arg1[seq[idx]] )
idx = k;
Vector p( pref[seq[idx]], arg1[seq[idx]] );
while( t > 1 && Cross( stk[t] - stk[t - 1], p - stk[t] ) <= 0 ) t --;
stk[++ t] = p;
}
rep( i, qL, qR ) {
Vector d( 1, ord[i] );
while( h < t && Cross( stk[h + 1] - stk[h], d ) >= 0 ) h ++;
if( stk[h].y - 1ll * stk[h].x * ord[i] <= A[ord[i]] ) tmp1[++ tot1] = ord[i];
else tmp2[++ tot2] = ord[i];
}
rep( i, 1, tot1 ) ord[qL + i - 1] = tmp1[i];
rep( i, 1, tot2 ) ord[qL + tot1 + i - 1] = tmp2[i];
Divide( tL, mid, qL, qL + tot1 - 1 );
Divide( mid + 1, tR, qR - tot2 + 1, qR );
}
void Work() {
rep( i, 1, N ) ord[i] = i;
Divide( 1, tot + 1, 1, N );
}
}
namespace Solve {
struct SegTree {
LL su[MAXN << 2], coe[MAXN << 2], tag[MAXN << 2];
SegTree(): su{}, coe{}, tag{} {}
inline void Cover( const int &x, const LL &nVal ) {
su[x] = coe[x] * nVal, tag[x] = nVal;
}
inline void Normalize( const int &x ) {
if( tag[x] == - 1e9 ) return ;
Cover( x << 1, tag[x] );
Cover( x << 1 | 1, tag[x] );
tag[x] = - 1e9;
}
void ModifyCoe( const int &x, const int &l, const int &r, const int &p, const LL &nVal ) {
if( l == r ) { coe[x] = nVal; return ; }
int mid = ( l + r ) >> 1; Normalize( x );
if( p <= mid ) ModifyCoe( x << 1, l, mid, p, nVal );
else ModifyCoe( x << 1 | 1, mid + 1, r, p, nVal );
coe[x] = coe[x << 1] + coe[x << 1 | 1];
}
void Cover( const int &x, const int &l, const int &r, const int &segL, const int &segR, const LL &nVal ) {
if( segL <= l && r <= segR ) { Cover( x, nVal ); return ; }
int mid = ( l + r ) >> 1; Normalize( x );
if( segL <= mid ) Cover( x << 1, l, mid, segL, segR, nVal );
if( mid < segR ) Cover( x << 1 | 1, mid + 1, r, segL, segR, nVal );
su[x] = su[x << 1] + su[x << 1 | 1];
}
LL Query( const int &x, const int &l, const int &r, const int &segL, const int &segR, const int &arg ) {
if( segL <= l && r <= segR ) return coe[x] * arg + su[x];
int mid = ( l + r ) >> 1; LL ret = 0; Normalize( x );
if( segL <= mid ) ret += Query( x << 1, l, mid, segL, segR, arg );
if( mid < segR ) ret += Query( x << 1 | 1, mid + 1, r, segL, segR, arg );
return ret;
}
void Build( const int &x, const int &l, const int &r, const LL *a, const int &typ ) {
if( l > r ) return ;
tag[x] = - 1e9;
if( l == r ) {
su[x] = a == NULL ? 0 : a[l];
coe[x] = typ == 0 ? 0 : ( typ == 1 ? 1 : l );
return ;
}
int mid = ( l + r ) >> 1;
Build( x << 1, l, mid, a, typ );
Build( x << 1 | 1, mid + 1, r, a, typ );
su[x] = su[x << 1] + su[x << 1 | 1];
coe[x] = coe[x << 1] + coe[x << 1 | 1];
}
};
struct Node {
int l, r; LL x0, y0;
Node(): l( 0 ), r( 0 ), x0( 0 ), y0( 0 ) {}
Node( int L, int R, LL X, LL Y ): l( L ), r( R ), x0( X ), y0( Y ) {}
};
SegTree tre[2];
Node stk[MAXN]; int top = 0;
void Work() {
tre[0].Build( 1, 1, N, A, 1 );
tre[1].Build( 1, 1, N, NULL, 2 );
rep( i, 1, Q ) {
if( opt[i] == 1 )
for( const int &x : chg[i] )
tre[0].Cover( 1, 1, N, x, x, 0 ),
tre[1].ModifyCoe( 1, 1, N, x, 0 );
if( opt[i] == 3 ) {
int l = arg1[i], r = arg2[i];
ans[i] += tre[1].Query( 1, 1, N, l, r, pref[i] ) + tre[0].Query( 1, 1, N, l, r, 0 );
}
}
tre[0].Build( 1, 1, N, NULL, 0 );
tre[1].Build( 1, 1, N, NULL, 0 );
rep( i, 1, N ) stk[++ top] = Node( i, i, 0, B[i] );
rep( i, 1, Q ) {
if( opt[i] == 1 ) {
for( const int &x : chg[i] )
tre[0].ModifyCoe( 1, 1, N, x, +1 ),
tre[1].ModifyCoe( 1, 1, N, x, +x );
LL v = arg1[i];
if( stk[top].y0 + 1ll * N * ( pref[i] - stk[top].x0 ) < v ) continue;
while( top && stk[top].y0 + 1ll * stk[top].l * ( pref[i] - stk[top].x0 ) >= v ) top --;
if( top && stk[top].y0 + 1ll * stk[top].r * ( pref[i] - stk[top].x0 ) >= v ) {
int l = stk[top].l, r = stk[top].r, mid;
while( l < r ) {
mid = ( l + r ) >> 1;
if( stk[top].y0 + 1ll * mid * ( pref[i] - stk[top].x0 ) >= v ) r = mid;
else l = mid + 1;
}
stk[top].r = l - 1;
stk[++ top] = Node( l, N, pref[i], v );
tre[0].Cover( 1, 1, N, l, N, v );
tre[1].Cover( 1, 1, N, l, N, - pref[i] );
} else {
top ++, stk[top] = Node( stk[top - 1].r + 1, N, pref[i], v );
tre[0].Cover( 1, 1, N, stk[top].l, N, v );
tre[1].Cover( 1, 1, N, stk[top].l, N, - pref[i] );
}
}
if( opt[i] == 3 ) {
int l = arg1[i], r = arg2[i];
ans[i] += tre[1].Query( 1, 1, N, l, r, pref[i] ) + tre[0].Query( 1, 1, N, l, r, 0 );
}
}
}
}
int main() {
// freopen( "ds.in", "r", stdin );
// freopen( "ds.out", "w", stdout );
Read( N ), Read( Q );
rep( i, 1, N ) Read( A[i] ), B[i] = Max( B[i - 1], A[i] );
rep( i, 1, Q ) {
Read( opt[i] );
if( opt[i] == 1 ) Read( arg1[i] );
if( opt[i] == 3 ) Read( arg1[i] ), Read( arg2[i] );
pref[i] = pref[i - 1] + ( opt[i] == 2 );
if( opt[i] == 1 ) seq[++ tot] = i;
}
FindChange :: Work();
Solve :: Work();
rep( i, 1, Q ) if( opt[i] == 3 ) Write( ans[i] ), putchar( '\n' );
return 0;
}