专栏文章

P2787 语文1(chin1)- 理理思维 题解

P2787题解参与者 7已保存评论 6

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
6 条
当前快照
1 份
快照标识符
@mio0f391
此快照首次捕获于
2025/12/02 11:19
3 个月前
此快照最后确认于
2025/12/02 11:19
3 个月前
查看原文

1. 题意解释

给出一个字符串,你可以对其进行以下三种操作:
  1. 获取第 xx 到第 yy 个字符中字母 kk 出现了多少次。
  2. 将第 xx 到第 yy 个字符全部赋值为字母 kk
  3. 将第 xx 到第 yy 个字符按照 aza∼z 的顺序排序。

2. 题意解释

把字符串看错一个由字符组成的数组,这道题就成了区间覆盖+区间排序问题。
容易想到用线段树解决。
每个线段维护一个 cntcnt 数组和 tagtag 标记,cnticnt_i 表示第 ii 个字母出现的次数,tagtag 表示这个区间内的所有字母都为同一个值,即编号为 tagtag 的字母。
这样一来操作一和操作二就是区间查询和区间覆盖,重点考虑操作三。
由于值域是有限的,我们不难想到桶排序。
而且我们的线段树已经维护了区间内的桶。
于是我们也就可以区间查询+区间覆盖解决区间排序问题。
到现在,这道题就成线段树板子题了。
不理解的同学看代码注释噻。

3. 代码实现

一个需要注意的点:输入中可能同时出现大小写字母,记住把它们全部转化为统一的大写或小写!
AC code:
CPP
#include<bits/stdc++.h>
using namespace std;
int n,m,op,x,y;
char k;
string s;
struct segtree{
	int l,r,cnt[30],tag;
}t[200200];
void build(int p,int l,int r){
	t[p].l=l,t[p].r=r,t[p].tag=0;
	if(l==r){
		t[p].cnt[s[l-1]-'A'+1]=1;
		return ;
	}
	int mid=(l+r)>>1;
	build(p*2,l,mid);
	build(p*2+1,mid+1,r);
	for(int i=1;i<=26;i++){
		t[p].cnt[i]=t[p*2].cnt[i]+t[p*2+1].cnt[i];
	}
}
void pushdown(int p){//懒标记下传
	if(t[p].tag){
		t[p*2].cnt[t[p].tag]=t[p*2].r-t[p*2].l+1;
		t[p*2+1].cnt[t[p].tag]=t[p*2+1].r-t[p*2+1].l+1;//子节点计数更新,将编号为 tag 的字母数更新为区间长度(因为区间被这个字母覆盖了)
		for(int i=1;i<=26;i++){
			if(i!=t[p].tag){
				t[p*2].cnt[i]=0;
				t[p*2+1].cnt[i]=0;
			}
		}////子节点计数更新,其余字母数量全部为0
		t[p*2].tag=t[p].tag;
		t[p*2+1].tag=t[p].tag;//标记下传
		t[p].tag=0;//父节点标记清空
	}
}
void change(int p,int l,int r,int c){//区间覆盖
	if(l>r){
		return ;
	}
	if(l<=t[p].l&&t[p].r<=r){
		t[p].tag=c;
		t[p].cnt[c]=t[p].r-t[p].l+1;
		for(int i=1;i<=26;i++){
			if(i!=c){
				t[p].cnt[i]=0;
			}
		}//更新标记和计数,原有的标记直接覆盖
		return ; 
	}
	pushdown(p);
	int mid=(t[p].l+t[p].r)>>1;
	if(l<=mid){
		change(p*2,l,r,c);
	}
	if(r>mid){
		change(p*2+1,l,r,c);
	} 
	for(int i=1;i<=26;i++){
		t[p].cnt[i]=t[p*2].cnt[i]+t[p*2+1].cnt[i];
	}
}
int ask(int p,int l,int r,int c){//区间查询
	if(l<=t[p].l&&t[p].r<=r){
		return t[p].cnt[c];
	}
	pushdown(p); 
	int mid=(t[p].l+t[p].r)>>1,val=0;
	if(l<=mid){
		val+=ask(p*2,l,r,c);
	}
	if(r>mid){
		val+=ask(p*2+1,l,r,c);
	}
	return val;
}
int main(){
	cin>>n>>m;
	cin>>s;
	for(int i=0;i<s.size();i++){
		if(s[i]>='a'&&s[i]<='z'){
			s[i]=s[i]+'A'-'a';
		}
	}//记得把字符串统一转换为大写或小写,我这里用的是大写
	build(1,1,n);
	for(int i=1;i<=m;i++){
		cin>>op;
		if(op==1){
			cin>>x>>y>>k;
			if(k>='a'&&k<='z'){
				k=k+'A'-'a';//把字母转换为大写
			}
			cout<<ask(1,x,y,k-'A'+1)<<endl;
		}
		if(op==2){
			cin>>x>>y>>k;
			if(k>='a'&&k<='z'){
				k=k+'A'-'a';//把字母转换为大写
			}
			change(1,x,y,k-'A'+1);
		}
		if(op==3){
			cin>>x>>y;
			int num[30];
			memset(num,0,sizeof num);
			for(int i=1;i<=26;i++){
				num[i]=num[i-1]+ask(1,x,y,i);
			}//查询前i个字母的数量总和,方便下面更新
			for(int i=1;i<=26;i++){
				change(1,x+num[i-1],x+num[i]-1,i);
			}//更新整个区间内的字母,第i个字母的范围为x+num[i-1]至x+num[i]-1
		}
	}
	return 0;
}

评论

6 条评论,欢迎与作者交流。

正在加载评论...