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<ak∨ak<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<ai∧ax<aj 使得
i
,
j
i,j
i,j 间接联通。
- 若 a k < a x a_k<a_x ak<ax,则有 k − j k-j k−j 联通。
- 若 a x < a k a_x<a_k ax<ak,则有 k − x k-x k−x 联通。
所以一个连通块一定对应的是数组 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{ai∣i∈[l1,r1]}>max{ai∣i∈[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{ai∣i≤k}>max{ai∣k<i}。
不妨将 a i ≥ a k a_i\ge a_k ai≥ak 的位置设为 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{ai∣k<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{ai∣i∈[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{ai−1,ai},max{ai−1,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{ai−1,ai},max{ai−1,ai}) 时 a i − 1 , a i a_{i-1},a_i ai−1,ai 会构成一对 01 01 01,在线段树上将这个区间整体 + 1 +1 +1 即可。
注意到 a i − 1 , a i + 1 a_{i-1},a_{i+1} ai−1,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;
}