CF1270H Number of Components(线段树)

problem

洛谷链接

solution

定理 i < j i<j i<j i , j i,j i,j 联通,则必有 k ∈ ( i , j ) k\in(i,j) k(i,j) 也与 i , j i,j i,j 联通。

下面给出证明,挺显然的。

  • a i < a j a_i<a_j ai<aj,则一定有 a i < a k ∨ a k < a j a_i<a_k\vee a_k<a_j ai<akak<aj 成立。
  • a i > a j a_i>a_j ai>aj,则一定有一个 x < i x<i x<i 满足 a x < a i ∧ a x < a j a_x<a_i\wedge a_x<a_j ax<aiax<aj 使得 i , j i,j i,j 间接联通。
    • a k < a x a_k<a_x ak<ax,则有 k − j k-j kj 联通。
    • a x < a k a_x<a_k ax<ak,则有 k − x k-x kx 联通。

所以一个连通块一定对应的是数组 a a a 的一段连续区间。

也就是说,所有连通块将数组划分成若干个互不相交且并集为整个数组的区间。

考虑相邻两个连通块,假设前一个联通块的左右端点为 l 1 , r 1 l_1,r_1 l1,r1,则后一个联通块的左右端点为 r 1 + 1 ( l 2 ) , r 2 r_1+1(l_2),r_2 r1+1(l2),r2

当且仅当 min ⁡ { a i ∣ i ∈ [ l 1 , r 1 ] } > max ⁡ { a i ∣ i ∈ [ l 2 , r 2 ] } \min\{a_i|i\in [l_1,r_1]\}>\max\{a_i|i\in[l_2,r_2]\} min{aii[l1,r1]}>max{aii[l2,r2]} 两个联通块才不会合并。

进一步讲,点 k k k 能够成为一个连通块的分断点,当且仅当 min ⁡ { a i ∣ i ≤ k } > max ⁡ { a i ∣ k < i } \min\{a_i|i\le k\}>\max\{a_i|k<i\} min{aiik}>max{aik<i}

不妨将 a i ≥ a k a_i\ge a_k aiak 的位置设为 1 1 1 a i < a k a_i<a_k ai<ak 的设为 0 0 0,将这个新数组表示为 f ( k ) f(k) f(k),与 k k k 有关。

则发现,当 k k k 为分断点的时候, f ( k ) f(k) f(k) 的长相一定是 111...11 ⏟ k 000...00 \underbrace{111...11}_{k}000...00 k 111...11000...00

换言之,当 k k k 为分断点的时候, f ( k ) f(k) f(k) a i ≠ a i + 1 a_i\neq a_{i+1} ai=ai+1 i i i 有且仅有一个。

当然 f ( k ) f(k) f(k) 的长相有很多种。但如果 k k k 成为分断点那么 f ( k ) f(k) f(k) 就只能有一种长相。

换个角度讲,令 w = max ⁡ { a i ∣ k < i } w=\max\{a_i|k<i\} w=max{aik<i},将 > w >w >w 的位置设为 1 1 1 ≤ w \le w w 的设为 0 0 0,将新数组依旧表示为 f ( w ) f(w) f(w)

f ( w ) f(w) f(w) 的长相一定也是 111...11 ⏟ k 000...00 \underbrace{111...11}_{k}000...00 k 111...11000...00

确定一个满足条件的 w w w 后的 k k k 也是唯一的。所以没必要枚举断点 k k k,只需要枚举 w w w 即可。

也就是说,只需要统计有多少个 w w w 对应的 f ( w ) f(w) f(w) 长相是这样的,即有且仅有一对相邻的 10 10 10

为了规避全 0 0 0 和全 1 1 1,也就是 w = min ⁡ / max ⁡ { a i ∣ i ∈ [ 1 , n ] } w=\min/\max\{a_i|i\in[1,n]\} w=min/max{aii[1,n]} 的情况,这个时候是没有一对 10 10 10 的。

不妨令 a 0 = ∞ , a n + 1 = 0 a_0=∞,a_{n+1}=0 a0=,an+1=0。则满足条件的 w w w f ( w ) f(w) f(w) 有且仅有一对相邻的 10 10 10

以权值 w w w 为下标建立线段树,记录每个叶子对应 f ( x ) f(x) f(x) 长相中 10 10 10 的对数。

最后统计多少个位置的对数为 1 1 1 即可。

考虑修改 a i a_i ai ,会对 w ∈ [ min ⁡ { a i − 1 , a i } , max ⁡ { a i − 1 , a i } ) ⋃ [ min ⁡ { a i , a i + 1 } , max ⁡ { a i , a i + 1 } ) w\in\Big[\min\{a_{i-1},a_i\},\max\{a_{i-1},a_{i}\}\Big)\bigcup\Big[\min\{a_{i},a_{i+1}\},\max\{a_{i},a_{i+1}\}\Big) w[min{ai1,ai},max{ai1,ai})[min{ai,ai+1},max{ai,ai+1}) 造成贡献变化。

例如 w ∈ [ min ⁡ { a i − 1 , a i } , max ⁡ { a i − 1 , a i } ) w\in\Big[\min\{a_{i-1},a_i\},\max\{a_{i-1},a_{i}\}\Big) w[min{ai1,ai},max{ai1,ai}) a i − 1 , a i a_{i-1},a_i ai1,ai 会构成一对 01 01 01,在线段树上将这个区间整体 + 1 +1 +1 即可。

注意到 a i − 1 , a i + 1 a_{i-1},a_{i+1} ai1,ai+1 可能越界,不妨设 a 0 = lim ⁡ , a n + 1 = 0 a_0=\lim,a_{n+1}=0 a0=lim,an+1=0,那么不管 i i i 位置,所有 w w w 至少都会有 1 1 1 01 01 01

线段树很难做到快速查询哪些位置是 1 1 1。但是现在所有的 w w w 01 01 01 个数 ≥ 1 \ge 1 1

就可以用线段树统计最小值和个数了。当且仅当最小值为 1 1 1 的时候再记录答案即可。

当然要注意,只有当前出现在 a a a 数组里的 w w w,即 w = a i w=a_i w=ai,才能在线段树中统计代表 w w w 的叶子节点是否贡献。

code

#include <bits/stdc++.h>
using namespace std;
#define maxn 500005
#define lim 1000001
int n, q;
int a[maxn];
struct node { int sum, tag, cnt; }t[lim + 5 << 2];

#define lson now << 1
#define rson now << 1 | 1
#define mid (l + r >> 1)

void pushup( int now ) {
    if( t[lson].sum < t[rson].sum ) 
        t[now].sum = t[lson].sum, t[now].cnt = t[lson].cnt;
    else if( t[lson].sum > t[rson].sum ) 
        t[now].sum = t[rson].sum, t[now].cnt = t[rson].cnt;
    else
        t[now].sum = t[lson].sum, t[now].cnt = t[lson].cnt + t[rson].cnt;
}

void pushdown( int now ) {
    if( t[now].tag ) {
        t[lson].sum += t[now].tag;
        t[rson].sum += t[now].tag;
        t[lson].tag += t[now].tag;
        t[rson].tag += t[now].tag;
        t[now].tag = 0;
    }
}

void modify( int now, int l, int r, int L, int R, int x ) {
    if( R < l or r < L ) return;
    if( L <= l and r <= R ) {
        t[now].sum += x;
        t[now].tag += x;
        return;
    }
    pushdown( now );
    modify( lson, l, mid, L, R, x );
    modify( rson, mid + 1, r, L, R, x );
    pushup( now );
}

void modify( int now, int l, int r, int pos, int x ) {
    if( l == r ) { t[now].cnt += x; return; }
    pushdown( now );
    if( pos <= mid ) modify( lson, l, mid, pos, x );
    else modify( rson, mid + 1, r, pos, x );
    pushup( now );
}

int query( int now, int l, int r, int L, int R ) {
    if( R < l or r < L ) return 0;
    if( L <= l and r <= R ) return t[now].sum == 1 ? t[now].cnt : 0;
    pushdown( now );
    return query( lson, l, mid, L, R ) + query( rson, mid + 1, r, L, R );
}

void modify( int l, int r, int x ) {
    if( l == r ) return;
    if( l > r ) swap( l, r );
    modify( 1, 0, lim, l, r - 1, x );
}

int main() {
    scanf( "%d %d", &n, &q );
    for( int i = 1;i <= n;i ++ ) scanf( "%d", &a[i] );
    a[0] = lim;
    for( int i = 0;i <= n;i ++ ) {
        modify( a[i], a[i + 1], 1 );
        modify( 1, 0, lim, a[i], 1 );
    }
    while( q -- ) {
        int pos, x;
        scanf( "%d %d", &pos, &x );
        modify( a[pos - 1], a[pos], -1 );
        modify( a[pos], a[pos + 1], -1 );
        modify( 1, 0, lim, a[pos], -1 );
        a[pos] = x;
        modify( a[pos - 1], a[pos], 1 );
        modify( a[pos], a[pos + 1], 1 );
        modify( 1, 0, lim, a[pos], 1 );
        printf( "%d\n", query( 1, 0, lim, 1, lim - 1 ) );
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值