[CF1270H]Number of Components

137 篇文章 1 订阅
11 篇文章 0 订阅

题目

传送门 to CF

思路

首先注意到 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 minixai>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 minixai>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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值