Codeforces 558E. A Simple Task (线段树+计数排序)

题意:

给定一个长度不超过10^5的字符串(小写英文字母),和不超过5000个操作。

每个操作 L R K 表示给区间[L,R]的字符串排序,K=1为升序,K=0为降序。

最后输出最终的字符串。

首先,发现每个元素的值只有26种,很自然的想到了计数排序。

由于线段树可以将任意区间分成不超过2*log2(n)个子区间。

所以,首先用线段树统计出所求区间的各个字符的数量。

然后按照排序的结果,依次将这些数存入分成的子区间,向上更新结果就好了。

复杂度O(26*q*log2(n))    (q为询问数量,n为字符串长度)


线段树节点定义如下:

<span style="font-size:14px;">struct Node{
	int d[26];//计数排序 
	int D;//总数
	bool sorted;//是否排好序 
	bool Inc;//是否升序
};
</span>

sorted 为  true  时,Inc 指示是升序还是降序。

sorted 标记是覆盖式标记,也就是说,一旦某节点sorted 为 true 那么这个节点的所有子节点的值都无效。

最后操作完,下推所有标记,然后输出字符串即可。


代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define out(i) <<#i<<"="<<(i)<<"  "
#define OUT1(a1) cout out(a1) <<endl
#define OUT2(a1,a2) cout out(a1) out(a2) <<endl
#define OUT3(a1,a2,a3) cout out(a1) out(a2) out(a3)<<endl
#define maxn 100007
#define ls l,m,rt<<1
#define rs m+1,r,rt<<1|1
using namespace std;
//std
int n,q;
char str[maxn];

//segment tree
struct Node{
	int d[26];//计数排序 
	int D;//总数
	bool sorted;//是否排好序 
	bool Inc;//是否升序
	void Insert(int v){ 
		memset(d,0,sizeof(d));
		D=d[v]=1;
		sorted=false;
	}
	void Take(bool Left,int N){//取本区间的N个数,Left指示从左开始还是从右开始 
		D=N;
		if(Left){
			for(int i=0;i<26;++i){
				if(N>=d[i]) N-=d[i];
				else{d[i]=N;N=0;}
			}
		}
		else{
			for(int i=25;i>=0;--i){
				if(N>=d[i]) N-=d[i];
				else{d[i]=N;N=0;}
			}
		}
	}
	void Drop(bool Left,int N){//去掉本区间的N个数,Left指示从左开始还是从右开始 
		D=D-N;
		if(Left){
			for(int i=0;i<26;++i){
				if(N>=d[i]) N-=d[i],d[i]=0;
				else{d[i]-=N;N=0;}
			}
		}
		else{
			for(int i=25;i>=0;--i){
				if(N>=d[i]) N-=d[i],d[i]=0;
				else{d[i]-=N;N=0;}
			}
		}
	}
	Node operator +(const Node &B)const{//重载加法,更新节点 
		Node C;
		C.sorted=false;
		for(int i=0;i<26;++i) C.d[i]=d[i]+B.d[i];
		C.D=D+B.D;
		return C;
	}
	void show(){
		printf("D=%d\n",D);
		for(int i=0;i<26;++i) {
			if(d[i]) printf("d[%d]=%d\n",i,d[i]);
		}
	}
}ST[maxn<<2];
void PushDown(int rt){//下推标记,直接覆盖子节点
	Node &L=ST[rt<<1],&R=ST[rt<<1|1];
	if(ST[rt].sorted){
		int N=L.D;
		L=ST[rt];
		L.Take(ST[rt].Inc,N);
		N=R.D;
		R=ST[rt];
		R.Take(!ST[rt].Inc,N);
		ST[rt].sorted=false;
	}
}
void Build(int l,int r,int rt){//建树 
	if(l==r){ST[rt].Insert(str[l]-'a');return;}
	int m=(l+r)>>1;
	Build(ls);
	Build(rs);
	ST[rt]=ST[rt<<1]+ST[rt<<1|1];
}
Node Query(int L,int R,int l,int r,int rt){//查询区间
	if(L <= l && r <= R){
		return ST[rt];
	}
	PushDown(rt);
	int m=(l+r)>>1;
	Node LANS,RANS; 
	int X=0;
	if(L <= m) LANS=Query(L,R,ls),X+=1;
	if(R >  m) RANS=Query(L,R,rs),X+=2;
	if(X==1) return LANS;
	if(X==2) return RANS;
	return LANS+RANS;
}
Node Sum;
void Update(int L,int R,int l,int r,int rt){
	if(L <= l && r <= R){
		int N=ST[rt].D;
		ST[rt]=Sum;
		ST[rt].Take(Sum.Inc,N);
		Sum.Drop(Sum.Inc,N);
		return;
	}
	int m=(l+r)>>1;
	if(L <= m) Update(L,R,ls);
	if(R >  m) Update(L,R,rs);
	ST[rt]=ST[rt<<1]+ST[rt<<1|1];
}
void Sort(int L,int R,int Inc){
	Sum=Query(L,R,1,n,1);//求出区间总和 
	Sum.sorted=true;Sum.Inc=Inc;//设置标记 
	Update(L,R,1,n,1);//分配总和给各个子区间,并且向上更新 
}
void Down(int l,int r,int rt){//下推所有标记
	if(l==r){
		for(int i=0;i<26;++i){
			if(ST[rt].d[i]){
				str[l]=i+'a';
				break;
			}
		}
		return;
	}
	PushDown(rt);
	int m=(l+r)>>1;
	Down(ls);
	Down(rs);
}
int main(void)
{
	while(~scanf("%d%d",&n,&q)){
		scanf("%s",str+1);
		Build(1,n,1);//建树
		for(int i=0;i<q;++i){
			int x,y,k;
			scanf("%d%d%d",&x,&y,&k);
			Sort(x,y,k);//操作
		}
		Down(1,n,1);//下推所有标记 
		printf("%s\n",str+1);
	}
return 0;
}


你可能感兴趣的:(Codeforces 558E. A Simple Task (线段树+计数排序))