题解:CF1270H Number of Components

题意:若 \(i < j, a_i < a_j\),则在 \(i,j\) 之间连边,定义一个序列权值为按上述方式连边后,数组中的连通块个数。现在每次对序列进行修改,同时输出序列权值。

做法:

对于这种看起来就很有性质的东西,我们一般来说先手玩一下观察性质,我们会发现,如果有一组 \((i,j)\) 满足条件,那么 \([i,j]\) 同属一个区间,证明的话直接分讨 \(a_k\) 的大小就行。

所以这个序列的连通块一定是分为 \([1,r_1],[l_2,r_2]\cdots [l_k, n]\) 这样的形式,等同于我们要做断点个数计数。

但是做到这里还不够,我们没有办法很方便的判断每个位置是否可以作为断点。

这里我们需要注意到第二个观察,一个位置 \(p\) 可以作为断点,当且仅当 \(\min\limits_{i=1}^p a_i > \max\limits_{i=p + 1}^n a_i\),这个比较显然,主要是直接猜的时候并不好观察出来。

做到这里还需要一个技巧,因为你直接维护前缀 \(\min\) 和后缀 \(\max\) 也很难快速处理出答案,我们考虑将答案进行一些转化。

我们枚举 \(\min\) 的值为 \(v\),将序列中 \(\ge v\) 的值标记为 \(1\),而 \(<v\) 的标记为 \(0\),那么我们会发现,如果序列如此标记后形如 \(11\cdots 100\cdots 0\),那么中间哪一个断点就是一个可行的位置,并且可以证明,如果我们枚举的 \(\min\) 都在序列中,那么不可能会有重复的断点,所以就等同于我们要计算有多少个在序列中的元素,满足标记后相邻的数不同的个数为 \(1\),这里为了方便统计,令 \(a_0 = \infty,a_{n+1}=-\infty\),并且将他们也进行标记。

那么之后就很简单了,我们考虑相邻的两个元素 \(a_p, a_{p+1}\)什么时候会出现 \(01,10\),当且仅当 \(v \in [\min({a_p}, a_{p+1}),\max(a_p,a_{p+1}))\) 的时候会,那么我们考虑用线段树维护每个 \(v\) 的答案即可。

如果求答案?注意到我们相邻不同的个数至少为 \(1\),所以我们在线段树节点上统计最小值及其个数即可,输出答案的时候注意特判最小值是否为 \(1\)

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 1e6 + 5, N = 1e6;
int n, dp[maxn], a[maxn], q;
struct node {
	int mn, val;
	node() {
		mn = val = 0;
	}
	node(int V, int C) {
		mn = V, val = C;
	}
	friend node operator+(node x, node y) {
		node ans; ans.mn = min(x.mn, y.mn);
		ans.val = x.val * (x.mn == ans.mn) + y.val * (y.mn == ans.mn);
		return ans;
	}
};
int use[maxn];
struct Segtree {
	node tr[maxn << 2];
	int tag[maxn << 2];
	void pushup(int t) {
		tr[t] = tr[t << 1] + tr[t << 1 | 1];
	}
	void build(int l, int r, int t) {
		if(l == r) {
			if(!use[l])
				tr[t] = node((int)(1e9), 0);
			else
				tr[t] = node(tr[t].mn, 1);
			return ;
		}
		int mid = l + r >> 1;
		build(l, mid, t << 1), build(mid + 1, r, t << 1 | 1);
		pushup(t);
	}
	void addtag(int t, int val) {
		tr[t].mn += val, tag[t] += val;
	}
	void pushdown(int t) {
		addtag(t << 1, tag[t]);
		addtag(t << 1 | 1, tag[t]);
		tag[t] = 0;
	}
	void update(int l, int r, int x, int y, int t, int val) {
		if(x <= l && r <= y) {
			addtag(t, val);
			return ;
		}
		int mid = l + r >> 1;
		pushdown(t);
		if(x <= mid)
			update(l, mid, x, y, t << 1, val);
		if(mid < y)
			update(mid + 1, r, x, y, t << 1 | 1, val);
		pushup(t);
	} 
	void modify(int l, int r, int pos, int t) {
		if(l == r) {
			if(!use[l])
				tr[t] = node((int)(1e9), 0);
			else
				tr[t] = node(tag[t], 1);
			return ;
		}
		int mid = l + r >> 1;
		pushdown(t);
		if(pos <= mid)
			modify(l, mid, pos, t << 1);
		else
			modify(mid + 1, r, pos, t << 1 | 1);
		pushup(t);
	}
	void out(int l, int r, int t) {
		if(l == r) {
			if(tr[t].mn == 0)
				cout << l << endl;
			return ;
		}
		int mid = l + r >> 1;
	//	pushdown(t);
	}
} tree;
void renew(int x, int val) {
	int l = min(a[x], a[x + 1]), r = max(a[x], a[x + 1]);
	tree.update(0, N, l, r - 1, 1, val);
}
signed main() {
	cin >> n >> q;
	for (int i = 1; i <= n; i++) 
		cin >> a[i], use[a[i]] = 1;	
	a[0] = 1e6 + 1, a[n + 1] = 0;
	tree.build(0, N, 1);
	for (int i = 0; i <= n; i++)
		renew(i, 1);
//	cout << tree.tr[1].mn << " " << tree.tr[1].val << endl;
	while(q--) {
		int p, v; cin >> p >> v;
		renew(p, -1), renew(p - 1, -1);
		use[a[p]] = 0; tree.modify(0, N, a[p], 1);
		a[p] = v;
		renew(p, 1), renew(p - 1, 1);
		use[a[p]] = 1, tree.modify(0, N, a[p], 1);
	//	cout << tree.tr[1].mn << " " << tree.tr[1].val << endl;
		if(tree.tr[1].mn == 1)
			cout << tree.tr[1].val << endl;
		else
			cout << 1 << endl;
	}
	return 0;
}
posted @ 2025-07-12 12:12  LUlululu1616  阅读(12)  评论(0)    收藏  举报