[CF1270H]Number of Components

题目

传送门 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 <iostream>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <cmath>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
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<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MAXN = 1000005;
int leaf[MAXN];
namespace SgTree{
    
    
	struct Node{
    
    
		int val, cnt;
		Node() = default;
		Node(int _v,int _c):val(_v),cnt(_c){
    
    }
		Node operator & (const Node &t) const {
    
    
			if(t.val > val) return *this;
			if(t.val < val) return t;
			return Node(val,cnt+t.cnt);
		}
	};
	Node data[MAXN<<2]; int tag[MAXN<<2];
	# define _ROOT_ int o=1,int l=1,int r=1000001
	# define LSON o<<1,l,(l+r)>>1
	# define RSON o<<1|1,((l+r)>>1)+1,r
	inline void pushUp(int o){
    
    
		data[o] = (data[o<<1] & data[o<<1|1]);
		data[o].val += tag[o];
	}
	void modify(int ql,int qr,int qv,_ROOT_){
    
    
		if(qr < l || r < ql) return ; // not relevant
		if(ql <= l && r <= qr) tag[o] += qv, data[o].val += qv;
		else modify(ql,qr,qv,LSON), modify(ql,qr,qv,RSON), pushUp(o);
	}
	void reload(int qid,_ROOT_){
    
    
		if(l == r) return void(data[o].cnt = leaf[l]);
		if((qid<<1) <= l+r) reload(qid,LSON), pushUp(o);
		else reload(qid,RSON), pushUp(o);
	}
	int query(){
    
     return data[1].val == 1 ? data[1].cnt : 0; }
};

int a[MAXN];
int main(){
    
    
	int n = readint(), q = readint();
	a[0] = 1000001, a[n+1] = 0;
	rep(i,1,n){
    
    
		leaf[a[i] = readint()] = 1;
		SgTree::reload(a[i]);
	}
	rep(i,1,n+1) if(a[i-1] > a[i])
		SgTree::modify(a[i]+1,a[i-1],1);
	for(int x; q; --q){
    
    
		x = readint(); leaf[a[x]] = 0; SgTree::reload(a[x]);
		if(a[x-1] > a[x]) SgTree::modify(a[x]+1,a[x-1],-1);
		if(a[x] > a[x+1]) SgTree::modify(a[x+1]+1,a[x],-1);
		a[x] = readint(); leaf[a[x]] = 1; SgTree::reload(a[x]);
		if(a[x-1] > a[x]) SgTree::modify(a[x]+1,a[x-1],1);
		if(a[x] > a[x+1]) SgTree::modify(a[x+1]+1,a[x],1);
		printf("%d\n",SgTree::query());
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_42101694/article/details/122414680