线段树求助,悬关
查看原帖
线段树求助,悬关
952621
ForMyLove楼主2023/5/28 11:02

rt,思路是建 26 棵线段树,具体思路见注释
可是本地运行样例爆栈,目前鉴定为 query 函数有问题,求助,谢谢

#include<iostream>
#define maxn 100001
using namespace std;
#define lson i<<1
#define rson i<<1|1
int n,m,tmp[maxn]; char c[maxn];
// tmp 起一个桶的作用,用于保存上次的查询结果(因为要推平)
struct node{ int l,r,t,lazy; }tree[27][maxn<<2]; 
//  t 用于维护 "该树维护的字母" 在 [l,r] 区间内出现的次数。
// 树的核心操作是查询与区间推平。
void pushup(int id,int i){ tree[id][i].t=tree[id][lson].t+tree[id][rson].t; }
void pushdown(int id,int i){ // 推平 pushdown 
	tree[id][lson].t=tree[id][i].lazy*(tree[id][lson].r-tree[id][lson].l+1);
	tree[id][rson].t=tree[id][i].lazy*(tree[id][rson].r-tree[id][rson].l+1);
	tree[id][lson].lazy=tree[id][rson].lazy=tree[id][i].lazy;
	tree[id][i].lazy=0;
}
void build(int id,int i,int l,int r){
	// id:树的标号(因为有26棵线段树)id:96 即为所维护的字母的 ASCII 
	tree[id][i].l=l,tree[id][i].r=r;
	if (l==r){
		if (c[l]-96==id){ //  c[l]是这棵树维护的字母。
			tree[id][i].t=1; 
		}
		else {
			tree[id][i].t=0;
		}
		return;
	} 
	int mid=l+r>>1;
	build(id,lson,l,mid);
	build(id,rson,mid+1,r);
	pushup(id,i);
}
int query(int id,int i,int l,int r){
	// 查询该字母在该区间内出现的次数,就是一个普通的查询
	if (l<=tree[id][i].l && tree[id][i].r<=r){
		return tree[id][i].t;
	} 
	pushdown(id,i);
	int mid=(tree[id][i].l+tree[id][i].r)>>1,val=0;
	if (l<=mid) val+=query(id,lson,l,r);
	if (r>mid)  val+=query(id,rson,l,r);
	return val;
}
void assign(int id,int i,int l,int r,int k){
	// 要对这段区间进行排序,就要先把它推平成0,即变成该区间内没有一个该字母的状态
	// 然后再次调用 assign 函数,根据查询结果重新推平赋值,把字母放到正确的区间。
	// assign 就是推平函数。 
	if (l<=tree[id][i].l && tree[id][i].r<=r){
		tree[id][i].t=k*(tree[id][i].r-tree[id][i].l+1);
		tree[id][i].lazy=k;
		return;
	} 
	pushdown(id,i);
	int mid=(tree[id][i].l+tree[id][i].r)>>1;
	if (mid>=l) assign(id,lson,l,r,k);
	if (mid<r)  assign(id,rson,l,r,k);	
	pushup(id,i);
}
int main(){
	cin>>n>>m>>c;
	for (int i=1;i<=26;i++){ // 26棵线段树一一建树。
		build(i,1,0,n-1); // char 数组下标从 0 开始 
	}
	int l,r,opt,sum;
	for (int i=1;i<=m;i++){
		cin>>l>>r>>opt;  // sum 用于标记已经推到了多少个位置 
		l-=1,r-=1,sum=1; // 因为 C数组从0开始,所以-1。
		if (opt==1){ // 升序 
			for (int i=1;i<=26;i++){ // 每棵树都要查一遍 
				tmp[i]=query(i,1,l,r); // 出现了多少次
				assign(i,1,l,r,0); // 推为 0  
				assign(i,1,sum,sum+tmp[i]-1,1); 
				/*  k 传 1?
				因为 assign 里面赋的值是 区间长*k,而不是直接赋 k 
				传 k 代表:在这个区间出现了 (sum+tmp[i]-1 -sum +1)*1 = tmp[i] 次
				而 tmp[i] 就是大区间内存在该字母的个数 */ 
			}
		} 
		if (opt==0){ // 降序:从 z 查到 a 
			for (int i=26;i>=1;i--){ // 每棵树都要查一遍 
				tmp[i]=query(i,1,l,r); // 出现了多少次
				assign(i,1,l,r,0); // 推为 0  
				assign(i,1,sum,sum+tmp[i]-1,1); 
			}
		} 		
	}
	// 暴力输出
	for (int i=1;i<=n;i++){ // 枚举整个字符串 
		for (int j=1;j<=26;j++){ // 搜 26 个字母 
			if (query(j,1,i,i)){
				cout<<char(j+96); break;
			}
		}
	} 
	return 0;
}

2023/5/28 11:02
加载中...