「CF1209G2」Into Blocks (hard version)
题目
点这里去往原网站。
给定一个长度为 \(n\) 的序列,每个位置上有颜色 \(a_i\)。
我们称序列是 good 的,当且仅当 \(\forall a_l = a_r,i\in [l, r]:a_i = a_l = a_r\),也即同一颜色在序列上的位置连续。
你可以依次执行一些操作使得序列变得 good,每次操作你可以将某颜色 \(c\) 在序列上的所有位置改成另一颜色 \(c'\)。
我们定义代价为,最终序列与原序列不同的位置数量(不是操作次数)。
你很轻易地就算出了最小代价。然而你发现,每当你算出答案时,序列又发生了修改,第 \(i_t\) 个位置上的颜色被改为了 \(x_t\)。
因此,你需要回答初始序列以及每次修改后的最小代价。需要注意的是,你所执行的操作不会保留到下一次询问。
对于 \(100\%\) 的数据,满足 \(1\le n,a_i,x_t\le 2\times 10^5, 0\le q\le 2\times 10^5, 1\le i_t\le n\)。
分析
首先,对问题的初步感知是:对于任意颜色 \(c\),设它出现位置的最左端为 \(l_c\),最右端为 \(r_c\),那么最终 \([l_c,r_c]\) 必须涂上相同的颜色。
根据这个性质我们可以写出一个简单的 \(O(n^2)\) DP。不过,我们可以更直接一些——发现序列一定可以被划分为若干个极小非空连续段,每个连续段内部的颜色只在该连续段内出现。这样,一个连续段的贡献为长度减去段内出现最多次的颜色的出现次数;因而,我们可以推断出,最优方案中极小非空连续段之间颜色一定是互异的。
从数值的角度考虑这个问题,如果 \(i\) 和 \(i+1\) 可以不属于同一个连续段,则必然不存在任何一个区间 \([l_c,r_c]\) 同时覆盖 \(i\) 和 \(i+1\)。我们设 \(b_i\) 表示有多少个区间 \([l_c,r_c]\) 同时覆盖 \(i\) 和 \(i+1\),则如果 \(b_i=0\),我们一定会将 \(i\) 和 \(i+1\) 分到两段中去。
不妨认为 \(b_0=0\),则我们可以取出一个排好序的序列 \(c\),满足 \(x\in c\Leftrightarrow b_x=0\)。考虑“相邻”的两个 \(b\)——\(b_{c_i}\) 和 \(b_{c_{i+1}}\),在原始序列上这就表示 \((c_i,c_{i+1}]\) 最终的颜色相同,因此我们只需要找出每对“相邻”的 \(b\) 之间的最大出现次数即可。
类似地,我们也需要将出现次数落到序列上——这很简单,根据上述信息,我们可以直接将颜色 \(c\) 的出现次数放到 \(l_c\) 的位置上,设之为 \(k_c\)。最终,问题变成了:维护所有 \(b=0\) 的相邻位置之间的 \(k\) 的最大值之和。
接下来的转化比较 trivial 了,由于 \(b\ge 0\),所以 \(b=0\) 的时候一定取到了最小值。我们不必维护 \(b=0\) 的位置,而只需要维护 \(b=b_{\min}\) 的位置的信息即可。
最后总结一下:我们需要单点修改 \(k\),并区间修改 \(b\);此外,我们需要维护所有 \(b=b_{\min}\) 的相邻位置之间的 \(k\) 的最大值之和。这些都可以用线段树 和 set 维护。
最终我们得到了 \(O((n+q)\log n)\) 的算法。
小结:
-
考试的时候,给每道题留出足够的时间,保证至少有 40 min;不要再出现“想到而来不及写的情况”。
-
多尝试将具象的信息、限制数值化,多思考信息、限制的等价形式或者强、弱化形式,找到最容易突破的那一个。
-
将信息集中到容易维护的结构上去。
代码
#include <set>
#include <cstdio>
#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 = 2e5 + 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 MAX( const _T a, const _T b ) {
return a > b ? a : b;
}
struct Element {
int mn, mx;
int lMx, rMx, su;
Element(): mn( INF ), lMx( - INF ), rMx( - INF ), su( 0 ) {}
Element( int V, int K ): mn( V ), mx( K ), lMx( K ), rMx( - INF ), su( 0 ) {}
Element( int V, int X, int L, int R, int S ): mn( V ), mx( X ), lMx( L ), rMx( R ), su( S ) {}
Element operator + ( const Element &q ) const {
Element ret;
if( mn < q.mn ) {
ret = *this;
ret.rMx = MAX( ret.rMx, q.mx );
} else if( mn > q.mn ) {
ret = q;
ret.lMx = MAX( ret.lMx, mx );
} else {
ret.mn = mn;
ret.lMx = lMx, ret.rMx = q.rMx;
ret.su = su + q.su + MAX( rMx, q.lMx );
}
ret.mx = MAX( mx, q.mx );
return ret;
}
};
std :: set<int> app[MAXN];
Element tre[MAXN << 2];
int tag[MAXN << 2];
int A[MAXN];
int N, Q;
inline void Upt( const int x ) { tre[x] = tre[x << 1] + tre[x << 1 | 1]; }
inline void Add( const int x, const int delt ) { tre[x].mn += delt, tag[x] += delt; }
inline void Normalize( const int x ) { if( tag[x] ) Add( x << 1, tag[x] ), Add( x << 1 | 1, tag[x] ), tag[x] = 0; }
void Update( const int x, const int l, const int r, const int segL, const int segR, const int delt ) {
if( segL > segR ) return ;
if( segL <= l && r <= segR ) { Add( x, delt ); return ; }
int mid = ( l + r ) >> 1; Normalize( x );
if( segL <= mid ) Update( x << 1, l, mid, segL, segR, delt );
if( mid < segR ) Update( x << 1 | 1, mid + 1, r, segL, segR, delt );
Upt( x );
}
void Modify( const int x, const int l, const int r, const int p, const int nVal ) {
if( l == r ) {
tre[x].mx = tre[x].lMx = nVal;
return ;
}
int mid = ( l + r ) >> 1; Normalize( x );
if( p <= mid ) Modify( x << 1, l, mid, p, nVal );
else Modify( x << 1 | 1, mid + 1, r, p, nVal );
Upt( x );
}
void Query() {
// Note that b_n always = 0
write( N - ( tre[1].su + tre[1].lMx ) ), putchar( '\n' );
}
void Remove( const int c ) {
if( app[c].empty() ) return ;
int l = * app[c].begin(),
r = * app[c].rbegin();
Modify( 1, 1, N, l, 0 );
Update( 1, 1, N, l, r - 1, -1 );
}
void Apply( const int c ) {
if( app[c].empty() ) return ;
int l = * app[c].begin(),
r = * app[c].rbegin();
Modify( 1, 1, N, l, app[c].size() );
Update( 1, 1, N, l, r - 1, 1 );
}
int main() {
read( N ), read( Q );
rep( i, 1, N ) {
read( A[i] );
app[A[i]].insert( i );
}
rep( i, 1, 2e5 ) Apply( i );
Query();
while( Q -- ) {
int p, x, c;
read( p ), read( x );
Remove( c = A[p] );
app[c].erase( p );
Apply( c );
Remove( c = A[p] = x );
app[c].insert( p );
Apply( c );
Query();
}
return 0;
}