D. Distinct Characters Queries

https://codeforces.com/problemset/problem/1234/D

题意:长度为n的字符串s,包含小写字母。q个操作,可能是将s中位置p的字符换成另一个,或者查询{l, r}不同字符的数量。

思路:线段树,单点修改区间查询,维护的updateNode类型考虑两种操作,插入字符或者更换字符。线段树节点维护一个长度为26的数组,如果某个位置>0,说明该字符在区间内出现过。

/*
* StaticSegmentTree(静态线段树)
* 设计思想:静态线段树主要用于先给出一组输入(一般这种输入确定了树中每个节点都会被访问到,所以直接静态开点),并且给出若干个修改和查询的情况。
* 基于面向对象的编程思想,本方法尽可能多的隐藏了内部实现的细节,并且将必要的编程接口暴露在外部,并需要对这些接口进行直接的修改。
* 
* 在该设计中,需要修改的接口有:
*                             UpdateNode:该结构体存储了区间上的操作类型:需要自定义结构体变量成员及构造函数;
*                                         懒人标记的向下传递方法:mergeLazyMarks();
*                                         懒人标记被更新后的方法:clear()。
*                             StaticSegmentTreeNode: 需要在该结构体中写出要维护的区间参数,初始化函数;
*                                                    该类重载了+来实现左右孩子区间合并:在重载函数中实现细节;
*                                                    实现区间上更新懒人标记值的方法:applyUpdate();
*                                                    建树必须指定模板参数True或False,代表是否使用懒人标记。
*                             静态线段树的主体所有必备的方法都已实现,无需修改,有的题目有剪枝操作请自行添加实现。
*
* gitHub(仓库地址): https://github.com/yxc-s/programming-template.git
*/










/*
该节点定义在区间上的操作,可以根据输入的类型重写该结构体。
*/
struct UpdateNode {

	/* 自定义区间要执行的操作变量。*/
	int value;
	int erase;


	/* 自定义初始化构造函数。*/
	UpdateNode(int value_ = 0, int ers = -1): value(value_), erase(ers) {
		
	}


	/*  懒人标记向下传递时调用,涉及区间操作必须实现。*/
	void mergeLazyMarks(const UpdateNode& parent_node, int segment_length) {

	}


	/* 清除懒人标记,涉及区间操作必须实现。 */
	void clear() {

	}
};





struct StaticSegmentTreeNode {

	/* 在区间上要维护的变量值,必须实现。*/
	std::array<int, 26> sum = {};


	/* 构造函数,必须实现。*/
	StaticSegmentTreeNode(){}


	/* 重载左右孩子区间合并操作,必须实现。*/
	friend StaticSegmentTreeNode operator + (const StaticSegmentTreeNode& a, const StaticSegmentTreeNode& b) {
		StaticSegmentTreeNode res{};
		for (int i = 0; i < 26; ++i) {
			res.sum[i] = a.sum[i] + b.sum[i];
		}
		return res;
	}


	/* 区间更新方法,必须实现。*/
	void applyUpdate(const UpdateNode& value, int segment_length) {
		sum[value.value] ++;
		if (value.erase != -1)
			sum[value.erase] --;
	}


};





/*
静态线段树,query返回类型必须是NODE_TYPE类型,更新数值类型必须是UpdateNode。
初始化模板必须指定true或者false。
该树总是约定区间左端点从1开始(避免(0 << 1)的情况)。
可通过树节点类型的数组初始化建树(推荐,建树时间复杂度更低)。
可通过要维护的区间的最大右端点下标来建树。
*/

template<const bool USE_LAZY_FLAG>
class StaticSegmentTree {
	using LAZY_TYPE      =      UpdateNode;
	using NODE_TYPE      =      StaticSegmentTreeNode;


public:
	StaticSegmentTree(unsigned int n) : n_(n) {
		st_.resize(4 * n_);
		if constexpr (USE_LAZY_FLAG){
			lazy_.resize(4 * n_);
			has_lazy_.resize(4 * n_);
		}
	}


	StaticSegmentTree(const std::vector<NODE_TYPE>& s) : n_(static_cast<int> (s.size()) - 1) {
		st_.resize(4 * n_);
		if constexpr (USE_LAZY_FLAG){
			lazy_.resize(4 * n_);
			has_lazy_.resize(4 * n_);
		}
		build(s, 1, 1, n_);
	}

	/* 单点更新。*/
	void update(int i, const LAZY_TYPE& value) {
		update(1, 1, n_, i, i, value);
	}

	/* 更新区间值。*/
	void update(int i, int j, const LAZY_TYPE& value) {
		update(1, 1, n_, i, j, value);
	}

	/* 获取区间节点。*/
	NODE_TYPE query(int i, int j) {
		return query(1, 1, n_, i, j);
	}


private:
	unsigned int n_;


	std::vector<NODE_TYPE>                st_;
	std::vector<LAZY_TYPE>                lazy_;
	std::vector<bool>                     has_lazy_;

	/* 区间更新。*/
	void update(int p, int l, int r, int i, int j, const LAZY_TYPE& value) {
		if constexpr (USE_LAZY_FLAG) { 
		propagate(p, l, r); 
		}
		if (i > j) { 
		return; 
		}
		if (l >= i && r <= j){
			if (USE_LAZY_FLAG == true){
				lazy_[p] = value;
				has_lazy_[p] = true;
				propagate(p, l, r);
				return;
			}
			else if (l == r){
				st_[p].applyUpdate(value, 1);
				return;
			}
		}
		int mid = (l + r) >> 1;
		update(p << 1, l, mid, i, std::min(mid, j), value);
		update(p << 1 | 1, mid + 1, r, std::max(mid + 1, i), j, value);
		st_[p] = st_[p << 1] + st_[p << 1 | 1];
	};

	/* 区间查询。*/
	NODE_TYPE query(int p, int l, int r, int i, int j) {
		if constexpr (USE_LAZY_FLAG) { 
			propagate(p, l, r); 
		}
		if (l >= i && r <= j) { 
			return st_[p]; 
		}
		int mid = (l + r) >> 1;
		if (j <= mid) {
			return query(p << 1, l, mid, i, j);
		}
		else if (i > mid) {
			return query(p << 1 | 1, mid + 1, r, i, j);
		}
		else {
			return (query(p << 1, l, mid, i, mid) + query(p << 1 | 1, mid + 1, r, mid + 1, j));
		}
	}

	/* 初始化构造。*/
	void build(const std::vector<NODE_TYPE>& s, int p, int l, int r) {
		if (l == r) { st_[p] = s[l]; }
		else {
			int mid = (l + r) >> 1;
			build(s, p << 1, l, mid);
			build(s, p << 1 | 1, mid + 1, r);
			st_[p] = st_[p << 1] + st_[p << 1 | 1];
		}
	}

	/* 懒人标记向下传播。*/
	void propagate(int p, int l, int r) {
		if (has_lazy_[p] == true) {
			st_[p].applyUpdate(lazy_[p], r - l + 1);
			if (l != r) {
				lazy_[p << 1].mergeLazyMarks(lazy_[p], r - l + 1);
				lazy_[p << 1 | 1].mergeLazyMarks(lazy_[p], r - l + 1);
				has_lazy_[p << 1] = has_lazy_[p << 1 | 1] = true;
			}
			has_lazy_[p] = false;
			lazy_[p].clear();
		}
	}


};


using StaticSegTree = StaticSegmentTree<false>;
using StaticSegNode = StaticSegmentTreeNode;
/*
ToDoList:

*/


using namespace std;

inline void preProcess() {

}


inline void solve() {
	string s;
	cin >> s;

	int n = (int)s.size();
	s = '!' + s;
	int q;
	cin >> q;

	StaticSegTree st(n + 1);
	for (int i = 1; i <= n; ++i) {
		st.update(i, {s[i] - 'a', -1});
	}

	while (q --) {
		int t;
		cin >> t;
		if (t == 1) {
			int p;
			cin >> p;
			char c;
			cin >> c;
			st.update(p, {c - 'a', s[p] - 'a'});
			s[p] = c;
		}
		else {
			int l, r;
			cin >> l >> r;

			auto node = st.query(l, r);
			int ans = 0;
			for (int i = 0; i < 26; ++i) {
				ans += node.sum[i] > 0;
			}
			cout << ans << '\n';
		}
	}
}
posted @ 2025-04-27 09:33  _Yxc  阅读(3)  评论(0)    收藏  举报