「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)\) 的算法。

小结:

  1. 考试的时候,给每道题留出足够的时间,保证至少有 40 min;不要再出现“想到而来不及写的情况”。

  2. 多尝试将具象的信息、限制数值化,多思考信息、限制的等价形式或者强、弱化形式,找到最容易突破的那一个。

  3. 将信息集中到容易维护的结构上去

代码

#include 
#include 

#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
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
void write( _T x ) {
    if( x < 0 ) putchar( '-' ), x = -x;
    if( 9 < x ) write( x / 10 );
    putchar( x % 10 + '0' );
}

template
_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 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;
}