题目
思路
首先注意到 a x < a y ( x < y ) a_x<a_y\;(x<y) ax<ay(x<y) 时, a k ( x < k < y ) a_k\;(x<k<y) ak(x<k<y) 要么与 x x x 有边、要么与 y y y 相连。所以连通块肯定是序列中连续的一段。
于是第一想法就是枚举分界点 x x x,要满足 min i ⩽ x a i > max x < i a i \min_{i\leqslant x}a_i>\max_{x<i}a_i mini⩽xai>maxx<iai 。然而这个东西要怎么动态维护呢?因为它 涉及整个序列,不太好搞;就算有一个楼房重建型的想法,也只能做到 O ( n log 2 n ) \mathcal O(n\log^2n) O(nlog2n),并且不是很好写……
思路一定要开阔!话是这么说,想不想的到又是另一回事了。感觉接下来的
m
o
t
i
v
a
t
i
o
n
\rm motivation
motivation 不太强烈,我也不知道为什么会想到这样做……我觉得可能是因为,全序关系只用判断相邻元素。条件
min
i
⩽
x
a
i
>
max
x
<
i
a
i
\min_{i\leqslant x}a_i>\max_{x<i}a_i
mini⩽xai>maxx<iai 其实就是说相邻权值的点之间连边的话,只有一条边会跨过
x
+
0.5
x+0.5
x+0.5 。
于是考虑相邻权值 a i , a j ( a i < a j ) a_i,a_j\;(a_i<a_j) ai,aj(ai<aj) 之间带来的影响。若 i < j i<j i<j,则 x ∈ ( i , j ) x\in(i,j) x∈(i,j) 都不会是合法分界点;若 j < i j<i j<i,则 x ∈ [ j , i ) x\in[j,i) x∈[j,i) 的 “被跨过” 次数 + 1 +1 +1 。最后只需要数出 “被跨过” 次数 = 1 =1 =1 的位置即可。但是怎么计数呢?
又是老套路:欲求特定值的数量只能分块;但如果特定值是 min \min min 或 max \max max,就有迹可循。考虑是否存在 “被跨过” 次数是 0 0 0 的位置?有。 x = n x=n x=n,或者 x x x 左侧(含 x x x 本身)的值都比右侧(不含 x x x 自己)的值大。第二种情况就是所谓的 “不可能合法” 情况,这时候我们可以让 x x x 的 “被跨过” 次数 + 2 +2 +2,既确保它是不会被统计的,同时也避免了 0 0 0 。而 x = n x=n x=n 本来就不是合法分界点,忽略就行。所以我们就统计 min \min min 即可,线段树可以维护了,复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn) 。
但是,还有可改进的地方。因为修改的是权值,而我们每次都要用相邻权值作考虑,有点麻烦;如果你发现这实际上是 二维偏序,你就会知道两个维度是等价的。具体来说,我们也可以考虑相邻的两个位置,方法与上面相同;这样就不用额外引入 s e t \rm set set 来维护相邻权值对了。
还有另一种想法:上面的 “被跨过” 次数 + 2 +2 +2 的操作是正确的,只是略显繁琐;可以考虑令 a 0 = + ∞ , a n + 1 = − ∞ a_0=+\infty,\;a_{n+1}=-\infty a0=+∞,an+1=−∞ 。这样就没有 x = n x=n x=n 的边界情况,也不可能左侧的值都比右侧的小了;而判断条件还是 “被跨过” 次数 = 1 =1 =1 。
代码
代码中当然既使用了 优化
,又使用了 另一种想法
。
#include <cstdio>
#include <cctype> // isdigit
using llong = long long;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar()) if(c == '-') f = -f;
for(; isdigit(c); c=getchar()) a = a*10+(c^48);
return a*f;
}
const int MAXN = 1000005;
namespace zkw{
struct Node { int v, c; };
Node operator & (const Node &a, const Node &b){
if(a.v == b.v) return Node{a.v,a.c+b.c};
return a.v < b.v ? a : b;
}
int tag[MAXN*3], ass; Node v[MAXN*3];
void build(const int &n){
for(ass=1; ass<n+2; ass<<=1);
}
void pushUp(const int &o){
v[o] = v[o<<1]&v[o<<1|1], v[o].v += tag[o];
}
void modify(int l, int r, int qv){
for(l=(l-1)^ass,r=(r+1)^ass; (l^r)!=1; ){
if(!(l&1)) v[l^1].v += qv, tag[l^1] += qv;
if(r&1) v[r^1].v += qv, tag[r^1] += qv;
l >>= 1, pushUp(l), r >>= 1, pushUp(r);
}
while(l >>= 1) pushUp(l);
}
inline void reload(int i, int qv){
for(i^=ass,v[i].c=qv; i>>=1; ) pushUp(i);
}
const int INF = 0x3fffffff;
Node query(int l, int r){
Node lres = Node{INF,0}, rres = lres;
for(l=(l-1)^ass,r=(r+1)^ass; (l^r)!=1; ){
if(!(l&1)) lres = lres&v[l^1];
if(r&1) rres = rres&v[r^1];
l >>= 1, lres.v += tag[l];
r >>= 1, rres.v += tag[r];
}
for(lres=lres&rres; l>>=1; ) lres.v += tag[l];
return lres;
}
int query(const int &n){
Node res = query(1,n);
return res.v == 1 ? res.c : 0;
}
}
const int MAXA = 1000001;
int a[MAXN];
void reborn(const int &x, const int &qv){
if(a[x-1] > a[x]) zkw::modify(a[x]+1,a[x-1],qv);
if(a[x] > a[x+1]) zkw::modify(a[x+1]+1,a[x],qv);
}
int main(){
int n = readint(), q = readint();
zkw::build(MAXA);
a[0] = MAXA, a[n+1] = 0;
rep(i,1,n){
a[i] = readint();
zkw::reload(a[i],1);
}
rep(i,0,n) if(a[i] > a[i+1])
zkw::modify(a[i+1]+1,a[i],1);
for(int x; q; --q){
x = readint();
zkw::reload(a[x],0), reborn(x,-1);
a[x] = readint();
zkw::reload(a[x],1), reborn(x,1);
printf("%d\n",zkw::query(MAXA));
}
return 0;
}