20230621-Segment Tree 1

发布时间 2023-07-19 22:51:30作者: H_W_Y

20230621

Segment Tree

在写线段树前想想:
1.每个区间需要记录哪些值?
2.需要哪些标记?
3.如何叠加标记(在原有标记的区间增加新的标记?)
4.如何对区间进行整体修改?
5.如何合并区间?

P1471 方差

题目大意

传送门

给定一个序列,要求支持:区间加,区间平均数,区间方差。
其中方差定义为:
\(s^2=\frac{1}{n} \sum _{i=1}^{n} (x_i-x)^2,n,m \le 10^5\)

Solution

很基础的线段树
维护平均数就只需要维护区间和区间个数
再来考虑方差
先把\(\frac{1}{n}\)提出去
\(\sum{(x_i-x)^2}\)
\(=\sum(x_i^2+x^2-2*x*x_i)\)
\(=\sum x_i^2+n*x^2-2*x* \sum x_i\)
这样就可以在修改时利用区间和\(sum\)算方差了
所以我们只需要维护\(s=\sum (x_i)^2\)

在区间修改时
可以用同样的方法转化式子
\(\sum (x_i+val)^2=\sum x_i^2+n* val^2+2* val \sum x_i\)
我们就可以通过之前的\(sum\)\(s\)推出了
于是这道题就做完了

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;
#define mid (l+r)/2
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1

const int maxn=1e5+10;
int n,m,op,x,y;
double a[maxn],v,sum,s;
struct node{
  double sum,s,val,tag;
  int l,r;
}tr[maxn*4];

void pushup(int rt){
  tr[rt].sum=tr[rt<<1].sum+tr[rt<<1|1].sum;
  tr[rt].s=tr[rt<<1].s+tr[rt<<1|1].s;
}

void build(int l,int r,int rt){
  tr[rt].l=l;tr[rt].r=r;
  if(l==r){
    tr[rt].val=a[l];
    tr[rt].sum=a[l];
    tr[rt].s=a[l]*a[l];
	tr[rt].tag=0;
    return ;
  }
  build(lson);build(rson);
  pushup(rt);
}

void pushdown(int rt){
  double t=tr[rt].tag;
  tr[rt].tag=0;
  tr[rt<<1].tag+=t;
  tr[rt<<1|1].tag+=t;
  tr[rt<<1].s=tr[rt<<1].s+(tr[rt<<1].r-tr[rt<<1].l+1)*t*t+2*t*tr[rt<<1].sum;
  tr[rt<<1|1].s=tr[rt<<1|1].s+(tr[rt<<1|1].r-tr[rt<<1|1].l+1)*t*t+2*t*tr[rt<<1|1].sum;
  tr[rt<<1].sum+=(tr[rt<<1].r-tr[rt<<1].l+1)*t;
  tr[rt<<1|1].sum+=(tr[rt<<1|1].r-tr[rt<<1|1].l+1)*t;  
}

void update(int l,int r,int rt,int x,int y,double val){
  if(x<=l&&y>=r){
  	tr[rt].tag+=val;
  	tr[rt].s=tr[rt].s+(r-l+1)*val*val+2*val*tr[rt].sum;
  	tr[rt].sum=tr[rt].sum+(r-l+1)*val;
  	return;
  }
  if(tr[rt].tag) pushdown(rt);
  if(x<=mid) update(lson,x,y,val);
  if(y>mid) update(rson,x,y,val);
  pushup(rt);
}

void query(int l,int r,int rt,int x,int y,double &sum,double &s){
  if(x<=l&&y>=r){
  	sum+=tr[rt].sum;
  	s+=tr[rt].s;
  	return ;
  }
  if(tr[rt].tag) pushdown(rt);
  if(x<=mid) query(lson,x,y,sum,s);
  if(y>mid) query(rson,x,y,sum,s);
}

int main(){
  /*2023.6.21 hewanying P1471 方差 线段树*/ 
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++) scanf("%lf",&a[i]);
  build(1,n,1);
  for(int i=1;i<=m;i++){
  	scanf("%d",&op);
  	sum=0,s=0;
  	if(op==1){
  	  scanf("%d%d%lf",&x,&y,&v);
	  update(1,n,1,x,y,v);	
	}
	if(op==2){
	  scanf("%d%d",&x,&y);
	  query(1,n,1,x,y,sum,s);
	  printf("%.4lf\n",(double)sum/(y-x+1));
	}
	if(op==3){
	  scanf("%d%d",&x,&y);
	  query(1,n,1,x,y,sum,s);
	  printf("%.4lf\n",(double)((double)(s-sum*2*(sum/(y-x+1)))/(y-x+1)+(double)(sum/(y-x+1))*(sum/(y-x+1))));
	}
  }
  return 0;
}

P1558 色板游戏

题目大意

传送门

给定一个区间,要求维护下列操作:把一个区间染上一个颜色;询问一个区间
的颜色个数,n, m ≤ 105,颜色数不多于 30。

Solution

发现颜色最多是30
所以开一个线段树暴力枚举就行了

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;
#define mid (l+r)/2
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1

const int maxn=1e5+10;
int n,m,q,ans=0;
struct node{
  int tag,num;
}tr[maxn*4][35];
bool vis[35];

void pushup(int rt){
  for(int i=1;i<=m;i++) 
    tr[rt][i].num=tr[rt<<1][i].num+tr[rt<<1|1][i].num;
}

void build(int l,int r,int rt){
  if(l==r){
  	for(int i=1;i<=m;i++) tr[rt][i].num=tr[rt][i].tag=0;
  	tr[rt][1].num=1;
  	return ;
  }
  build(lson);build(rson);
  pushup(rt);
}

void pushdown(int l,int r,int rt){
  for(int i=1;i<=m;i++){
  	if(tr[rt][i].tag==1){
  	  tr[rt][i].tag=0;
  	  for(int j=1;j<=m;j++) tr[rt<<1][j].num=tr[rt<<1|1][j].num=tr[rt<<1|1][j].tag=tr[rt<<1][j].tag=0;
  	  tr[rt<<1][i].tag=1;
	  tr[rt<<1|1][i].tag=1;
	  tr[rt<<1][i].num=mid-l+1;
  	  tr[rt<<1|1][i].num=r-mid;
  	  break;
	}
  }
}

void update(int l,int r,int rt,int a,int b,int x){
  if(a<=l&&b>=r){
  	for(int i=1;i<=m;i++) tr[rt][i].num=tr[rt][i].tag=0;
  	tr[rt][x].num=r-l+1;
  	tr[rt][x].tag=1;
  	return ;
  }
  pushdown(l,r,rt);
  if(a<=mid) update(lson,a,b,x);
  if(b>mid) update(rson,a,b,x);
  pushup(rt);
}

void query(int l,int r,int rt,int a,int b){
  if(a<=l&&b>=r){
  	for(int i=1;i<=m;i++) 
  	  if(tr[rt][i].num) vis[i]=true;
  	return ;
  }
  pushdown(l,r,rt);
  if(a<=mid) query(lson,a,b);
  if(b>mid) query(rson,a,b);
  return ;
}

int main(){
  /*2023.6.24 hewanying P1558 色板游戏 线段树*/ 
  scanf("%d%d%d",&n,&m,&q);
  build(1,n,1);
  for(int i=1;i<=q;i++){
  	char ch;int a,b,x;
  	scanf("\n%c",&ch);
  	if(ch=='C'){
  	  scanf("%d%d%d",&a,&b,&x);
	  if(a>b) swap(a,b);
	  update(1,n,1,a,b,x);	
	}
	else{
	  scanf("%d%d",&a,&b);
	  if(a>b) swap(a,b);
	  for(int j=1;j<=m;j++) vis[j]=false;
	  query(1,n,1,a,b);ans=0;
	  for(int j=1;j<=m;j++) if(vis[j]) ans++;
	  printf("%d\n",ans);
	}
  }
  return 0;
}

P1502 窗口的星星

题目大意

传送门

给定一个平面上的 \(n\) 个点,求一个 \(w × h\) 的矩形可以覆盖到的点数最多多少?
\(n \le 10^4,x,y \lt 2^{31},w,h\le 10^6\)

Solution

这道题和平面矩形有关
不难联想到扫描线
我们先把\(y\)离散化
将每个点按照\(x\)升序排序
然后把扫描线沿\(x\)从左到右扫过来
对于扫描线所在的位置\(x\)
我们希望维护从\(x-w+1 \sim x\)中间的点的\(y\)
不妨用线段树来维护
我们希望查询到长度为\(h\)的区间内价值的最大值

在线段树上,我们可以以\(y\)为区间
对于每一个点\(x,y\)
我们把\(y \sim y+h-1\)的区间内加上\(val\)
每一次pushup都取max
最后直接输出\(tr[1]\)就是答案

而考虑维护\(x\)的范围一直是\(w\)
我们可以利用差分的思想
对于每一个节点再建立一个节点\(x+w-1,y\)
权值为\(-val\)即可

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define mid (l+r)/2
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1

const int maxn=1e5+10;
int T,n,w,h,b[maxn];
struct node{
  int x,y,hy,ly,lhy;
  int val;
  bool operator < (const node &rhs) const{
    if(x!=rhs.x) return x<rhs.x;
    return val>rhs.val;
  }
}a[maxn];
int tr[maxn*4],ans=0,lazy[maxn*4];

void pushdown(int rt){
  if(lazy[rt]!=0){
  	lazy[rt<<1]+=lazy[rt];
  	lazy[rt<<1|1]+=lazy[rt];
  	tr[rt<<1]+=lazy[rt];
  	tr[rt<<1|1]+=lazy[rt];
    lazy[rt]=0;
  }
} 

void update(int l,int r,int rt,int a,int b,int x){
  if(a<=l&&b>=r){
  	tr[rt]+=x;
  	lazy[rt]+=x;
  	return;
  }
  pushdown(rt);
  if(a<=mid) update(lson,a,b,x);
  if(b>mid) update(rson,a,b,x);
  tr[rt]=max(tr[rt<<1],tr[rt<<1|1]);
}

signed main(){
  /*2023.6.24 hewanying P1502 窗口的星星 线段树*/ 
  scanf("%lld",&T);
  while(T--){
  	scanf("%lld%lld%lld",&n,&w,&h);
  	for(int i=1;i<=n;i++){
  	  scanf("%lld%lld%lld",&a[i].x,&a[i].y,&a[i].val);
  	  a[i].hy=a[i].y+h-1;
	  b[i]=a[i].y;b[n+i]=a[i].hy;	
	}
	sort(b+1,b+2*n+1);sort(a+1,a+n+1);
	int len=unique(b+1,b+2*n+1)-b-1;
	for(int i=1;i<=len*4;i++) tr[i]=lazy[i]=0;
	for(int i=1;i<=n;i++){
	  a[i].ly=lower_bound(b+1,b+len+1,a[i].y)-b;	
	  a[i].lhy=lower_bound(b+1,b+len+1,a[i].hy)-b;
	}
	queue<int> q;ans=0;
	for(int i=1;i<=n;i++){
	  while(!q.empty()&&a[q.front()].x+w<=a[i].x){
	  	int t=q.front();
	  	ans=max(ans,tr[1]);
	  	update(1,len,1,a[t].ly,a[t].lhy,-a[t].val);//注意线段树的范围是1,len,1而不是1,n,1,这可能会造成递归越界而MLE
		q.pop();
	  }
	  q.push(i);
	  update(1,len,1,a[i].ly,a[i].lhy,a[i].val);
	  ans=max(ans,tr[1]);
	}
	while(!q.empty()){
	  int t=q.front();
	  ans=max(ans,tr[1]);
	  update(1,len,1,a[t].ly,a[t].lhy,-a[t].val);
	  q.pop();
	}
	printf("%lld\n",ans);
  }
  return 0;
}

P1969 [NOIP2013 提高组] 积木大赛

题目大意

传送门
给定一个长度为 \(n\) 的序列,你现在需要清除它,每次可以选择一个区间全部减
\(1\),但不能减少已经是 \(0\) 的。求最小次数,\(n \le 10^5\)

Solution

我的想法是先排序
从左到右枚举这个数消失后会不会对这个序列的段数进行影响
如果它两边都已经没有了
那么\(cnt--\)
如果它两边都还有数
那么\(cnt++\)
于是有了\(O(n log n)\)的做法

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

const int maxn=1e5+10;
int n,ans=0,cnt=0;
bool vis[maxn];
struct node{
  int id,val;
  bool operator <(const node &rhs) const{
    if(val!=rhs.val) return val<rhs.val;
    return id<rhs.id;
  }
}a[maxn];

int main(){
  /*2023.7.2 H_W_Y P1969 [NOIP 2013提高组] 积木大赛 乱搞*/ 
  scanf("%d",&n);
  for(int i=1;i<=n;i++) scanf("%d",&a[i].val),a[i].id=i;
  cnt=1;
  sort(a+1,a+n+1);
  vis[0]=vis[n+1]=true;
  for(int i=1;i<=n;i++){
  	ans+=cnt*(a[i].val-a[i-1].val);
  	if(vis[a[i].id-1]&&vis[a[i].id+1]) cnt--;
  	else if(!vis[a[i].id-1]&&!vis[a[i].id+1]) cnt++;
  	vis[a[i].id]=true;
  }
  printf("%d\n",ans);
  return 0;
} 
紧接着,我看了看题解 原来我想复杂了 如果这个数比上一个数大 就加上$\Delta$即可 因为如果我比上一个数小 那在消上一个数是就顺便把我也消了 这样复杂度就是$O(n)$
H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

int n,x,ans=0,lst=0;

int main(){
  scanf("%d",&n);
  for(int i=1;i<=n;i++) scanf("%d",&x),ans+=(x>lst)?(x-lst):0,lst=x;
  printf("%d\n",ans);
  return 0;
} 

P2471 [SCOI2007] 降雨量

题目大意

传送门
给定 \(n\) 年的降雨量,部分年份降水量未知。询问 \(m\) 次,形如 \(X\) 年是否是 \(Y\)
以来降雨量最多的年份$(r_X \le r_Y, ∀Z \in [Y, X], r_Z \lt r_X) $,回答 \(true\)\(false\)\(maybe\)
\(n \le 5 × 10^4, m \le 10^5\)

Solution

静下心来分析和讨论
一共有9种情况
首先,不难想到是线段树
维护每一个区间的最大值
注意两个条件:

  1. \(x\)\(y\)年以来降雨量最多的,\(y+1 \sim x-1\)里都严格小于
  2. \(x\)年的降雨量不超过\(y\)

于是乎:
mx表示已知的年份中\(y+1 \sim x\)的最大降雨量
mx1表示已知的年份中\(y+1 \sim x-1\)的最大降雨量
(每一条都在前面几条不成立的条件下)

  1. \(x\)年不确定
  • \(y\)年不确定 \(\to maybe\)
  • \(y\)年确定
    • \(mx \ge y \to false\)
    • \(mx \lt y||y+1 \sim x中没有已知 \to maybe\)
  1. \(x\)年确定
  • \(y\)年确定
    • \(x.r>y.r \to false\)
    • \(x.r \ne mx||mx1==mx \to false\)
    • 全部已知 \(\to true\)
    • 还有未知 \(\to maybe\)
  • \(y\)年不确定
    • \(x.r \ne mx||mx1==mx \to flase\)
    • \(\to maybe\)

几种情况想清楚即可

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

const int maxn=1e5+10;
int n,m,x,y,l,r,b[maxn];
struct node{
  int y,r,id;
}a[maxn];
struct Seg{
  int val,id;
}tr[maxn*4],mx,mx1;

namespace SGT{
  #define mid (l+r)/2
  #define lson l,mid,rt<<1
  #define rson mid+1,r,rt<<1|1
  #define lc rt<<1
  #define rc rt<<1|1
  inline void pushup(int rt){
    if(tr[lc].val>=tr[rc].val) tr[rt]=tr[lc];
	else tr[rt]=tr[rc];  
  }
  inline void update(int x,int val,int l=1,int r=n,int rt=1){
  	if(l==r){
  	  tr[rt]=(Seg){val,x};
	  return;	
	}
	if(x<=mid) update(x,val,lson);
	else update(x,val,rson);
	pushup(rt); 
  }
  inline Seg Max(Seg p1,Seg p2){return p1.val>p2.val?p1:p2;}
  inline Seg query(int x,int y,int l=1,int r=n,int rt=1){
  	if(x<=l&&y>=r) return tr[rt];
  	Seg res=(Seg){0,0};
	if(x<=mid) res=Max(res,query(x,y,lson));
	if(y>mid) res=Max(res,query(x,y,rson));
	return res;
  }
}
using namespace SGT;

int main(){
  /*2023.7.3 H_W_Y P2471 [SCOI2007] 降雨量 Segment Tree*/ 
  scanf("%d",&n);
  for(int i=1;i<=n;i++){
  	scanf("%d%d",&a[i].y,&a[i].r);
	a[i].id=i,b[i]=a[i].y;
	update(i,a[i].r);
  }
  scanf("%d",&m);
  for(int i=1;i<=m;i++){
    scanf("%d%d",&y,&x);
    r=lower_bound(b+1,b+n+1,x)-b;
    l=lower_bound(b+1,b+n+1,y)-b;
    if(b[r]!=x){
      if(b[l]!=y) printf("maybe\n");
      else{
      	l++;r--;
      	if(l>r) printf("maybe\n");
      	else{
      	  mx=query(l,r);
      	  if(mx.val>=a[l-1].r) printf("false\n");
      	  else printf("maybe\n");      		
		}
	  }
	}
	else {
	  if(b[l]==y){
	  	if(a[r].r>a[l].r) printf("false\n");
	  	else{
	  	  l++;
		  mx=query(l,r);
		  if(l!=r) mx1=query(l,r-1);
		  else mx1.val=-0x3f3f3f3f;
		  if(mx.val!=a[r].r||mx.val==mx1.val) printf("false\n");
		  else{
		  	if(r-l+1==x-y) printf("true\n");
		  	else printf("maybe\n");
		  }	
		}
	  }
	  else{
	  	mx=query(l,r);
	  	if(l!=r) mx1=query(l,r-1);
	  	else mx1.val=-0x3f3f3f3f;
	  	if(mx.val!=a[r].r||mx.val==mx1.val) printf("false\n");
	  	else printf("maybe\n");
	  }
	}
  } 
  return 0;
} 

P2418 yyy loves OI IV

题目大意

传送门
给定 \(n\) 个数要求连续分组,每个数有颜色,共两种。要求每一块要么颜色相同,
要么两种颜色差的绝对值不超过 \(m\),求最少块数。\(n \le 5 × 10^5, m \le 2000\)

Solution

不难想到可以用dp来维护
考虑先分别维护1和2的前缀和\(sum1,sum2\)
\(dp[i]\)表示当前考虑到第\(i\)个的最小寝室数量
这样\(dp[i]\)就有三个来源:

  1. \(sum1[j]=sum1[i]\)
  2. \(sum2[j]=sum2[i]\)
  3. \(|(sum1[i]-sum1[j-1])-(sum2[i]-sum2[j-1])| \le m\)

考虑到前缀和是单调的
那么前面两个状态我们可以直接维护(每次取min)
而对于第三个,我们考虑把式子化简:
\(sum1[i]-sum2[i] \le sum1[j-1]-sum2[j-1]+m\)
\(sum1[i]-sum2[i] \ge sum1[j-1]-sum2[j-1]-m\)
我们令\(f[i]=sum1[i]-sum2[i]\)
那么我们要去找的是一个\(f[j-1]\)满足
\(f[i]-m \le f[j-1] \le f[i]+m\)
且我们希望这里的\(dp[j-1]\)越小越好

我们就把问题转化成了在以\(f[]\)数组为下标的数组上
找一个区间\(f[i]-m \sim f[i]+m\)中的\(dp[j]\)最小
就可以把问题转化到线段树上进行区间查询
每一次update在\(f[i]\)的位置上加入\(dp[i]\)
最后维护区间最小值即可

注意:
对于前两个状态我们不管\(a[i]\)是1还是2都要同时更新
否则若\(i\)是某个连续段的结尾,后面的答案就错了

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

const int maxn=5e5+10,inf=0x3f3f3f3f;
int n,m,sum[3][maxn],dp[maxn],ans[3][maxn],a[maxn],f[maxn];
struct seg{
  int l,r,val;
}tr[maxn*8];

namespace SGT{
  #define lc rt<<1
  #define rc rt<<1|1
  inline void pushup(int rt){tr[rt].val=min(tr[lc].val,tr[rc].val);}
  inline void build(int l,int r,int rt){
  	tr[rt].l=l;tr[rt].r=r;tr[rt].val=inf;
  	if(l==r) return ;
  	int mid=l+((r-l)>>1);
  	build(l,mid,lc);
  	build(mid+1,r,rc);
  }
  inline void update(int x,int val,int rt=1){
  	if(tr[rt].l==tr[rt].r){
      tr[rt].val=min(tr[rt].val,val);
	  return;	
	}
	int mid=tr[rt].l+((tr[rt].r-tr[rt].l)>>1);
	if(x<=mid) update(x,val,lc);
	else update(x,val,rc);
	pushup(rt);
  }
  inline int query(int a,int b,int rt=1){
  	if(a<=tr[rt].l&&b>=tr[rt].r) return tr[rt].val;
  	int res=inf;
  	int mid=tr[rt].l+((tr[rt].r-tr[rt].l)>>1);
  	if(a<=mid) res=min(res,query(a,b,lc));
  	if(b>mid) res=min(res,query(a,b,rc));
  	return res;
  }
}
using namespace SGT;

inline int read(){
  int x=0,ff=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') ff=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*ff;
}

int main(){
  /*2023.7.5 H_W_Y P2418 yyy loves OI IV 线段树+DP*/ 
  n=read();m=read();
  for(int i=1;i<=n;i++){
  	a[i]=read();
	sum[1][i]=sum[1][i-1]+(a[i]==1);
  	sum[2][i]=sum[2][i-1]+(a[i]==2);
  	f[i]=sum[1][i]-sum[2][i];
  }
  memset(dp,0x3f,sizeof(dp));
  memset(ans,0x3f,sizeof(ans));
  build(-500000,500000,1);
  update(0,0);
  dp[0]=0;
  ans[1][0]=ans[2][0]=0;
  for(int i=1;i<=n;i++){
	dp[i]=min(min(ans[2][sum[2][i]]+1,dp[i-1]+1),min(ans[1][sum[1][i]]+1,query(f[i]-m,f[i]+m)+1));
	ans[1][sum[1][i]]=min(ans[1][sum[1][i]],dp[i]);//两个都要更新
	ans[2][sum[2][i]]=min(ans[2][sum[2][i]],dp[i]);
  	update(f[i],dp[i]);
  }
  printf("%d\n",dp[n]);
  return 0;
}

P2572 [SCOI2010] 序列操作

题目大意

传送门
要求维护一个长度为 \(n\) 的二进制串,要求支持区间:全赋为 \(0\),全赋为 \(1\)\(01\)
取反,区间 \(1\) 个数,区间 \(1\) 连续个数。

Solution

一道线段树的裸题(好久没遇到了)
但是为什么是紫题
很恶心的线段树,特别考验心态……

注意反转标记和覆盖标记的顺序

  1. 覆盖标记在反转标记之上
    那么可以把反转标记清零
  2. 反转标记在覆盖标记之上
    那么可以对覆盖标记直接修改

注意在pushdown的时候
对于每一个反转标记rev也要进行特殊处理
不要忘记了

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

const int maxn=1e5+10;
int n,m,a[maxn],l,r,op;
struct seg{
  int cnt[2],q[2],h[2],mx[2],tag,l,r,rev;
}tr[maxn*4];

inline int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

namespace SGT{
  #define mid (l+r)/2
  #define lson l,mid,rt<<1
  #define rson mid+1,r,rt<<1|1
  #define lc rt<<1
  #define rc rt<<1|1
  inline void pushup(int rt){
  	tr[rt].cnt[0]=tr[lc].cnt[0]+tr[rc].cnt[0];
  	tr[rt].cnt[1]=tr[lc].cnt[1]+tr[rc].cnt[1];
  	tr[rt].q[0]=tr[lc].q[0];
  	tr[rt].q[1]=tr[lc].q[1];
  	if(tr[lc].q[0]==tr[lc].r-tr[lc].l+1) tr[rt].q[0]+=tr[rc].q[0];
  	if(tr[lc].q[1]==tr[lc].r-tr[lc].l+1) tr[rt].q[1]+=tr[rc].q[1];
  	tr[rt].h[0]=tr[rc].h[0];
  	tr[rt].h[1]=tr[rc].h[1];
  	if(tr[rc].h[0]==tr[rc].r-tr[rc].l+1) tr[rt].h[0]+=tr[lc].h[0];
  	if(tr[rc].h[1]==tr[rc].r-tr[rc].l+1) tr[rt].h[1]+=tr[lc].h[1];
  	tr[rt].mx[0]=max(tr[lc].mx[0],max(tr[rc].mx[0],tr[lc].h[0]+tr[rc].q[0]));
  	tr[rt].mx[1]=max(tr[lc].mx[1],max(tr[rc].mx[1],tr[lc].h[1]+tr[rc].q[1]));
  }
  inline void pushdown(int l,int r,int rt){
	if(tr[rt].tag==1){
	  tr[lc].tag=tr[rc].tag=1;tr[lc].rev=tr[rc].rev=0;
	  tr[lc].cnt[0]=tr[rc].cnt[0]=0;
	  tr[lc].q[0]=tr[rc].q[0]=tr[lc].h[0]=tr[rc].h[0]=tr[lc].mx[0]=tr[rc].mx[0]=0;
	  tr[lc].cnt[1]=tr[lc].q[1]=tr[lc].h[1]=tr[lc].mx[1]=mid-l+1;
	  tr[rc].cnt[1]=tr[rc].q[1]=tr[rc].h[1]=tr[rc].mx[1]=r-mid;
	  tr[rt].tag=0;
	}
	if(tr[rt].tag==2){
	  tr[lc].tag=tr[rc].tag=2;tr[lc].rev=tr[rc].rev=0;
	  tr[lc].cnt[1]=tr[rc].cnt[1]=tr[lc].q[1]=tr[rc].q[1]=tr[lc].h[1]=tr[rc].h[1]=tr[lc].mx[1]=tr[rc].mx[1]=0;
	  tr[lc].cnt[0]=tr[lc].q[0]=tr[lc].h[0]=tr[lc].mx[0]=mid-l+1;
	  tr[rc].cnt[0]=tr[rc].q[0]=tr[rc].h[0]=tr[rc].mx[0]=r-mid;
	  tr[rt].tag=0;
	}
	if(tr[rt].rev==1){
	  swap(tr[lc].cnt[0],tr[lc].cnt[1]);
	  swap(tr[lc].q[0],tr[lc].q[1]);
	  swap(tr[lc].h[0],tr[lc].h[1]);
	  swap(tr[lc].mx[0],tr[lc].mx[1]);
	  if(tr[lc].tag==1) tr[lc].tag=2;
	  else if(tr[lc].tag==2) tr[lc].tag=1;//注意pushdown里tag下传要特殊处理 
	  else tr[lc].rev^=1;  
	  swap(tr[rc].cnt[0],tr[rc].cnt[1]);
	  swap(tr[rc].q[0],tr[rc].q[1]);
	  swap(tr[rc].h[0],tr[rc].h[1]);
	  swap(tr[rc].mx[0],tr[rc].mx[1]);
	  if(tr[rc].tag==1) tr[rc].tag=2;
	  else if(tr[rc].tag==2) tr[rc].tag=1;
	  else tr[rc].rev^=1;
	  tr[rt].rev=0;
	}
  }
  inline void build(int l=1,int r=n,int rt=1){
  	tr[rt].l=l;tr[rt].r=r;
  	if(l==r){
  	  tr[rt].cnt[a[l]]=tr[rt].q[a[l]]=tr[rt].h[a[l]]=tr[rt].mx[a[l]]=1;
	  tr[rt].tag=0;
	  return;	
	}
	build(lson);build(rson);
	pushup(rt);
  }
  inline void update(int x,int y,int val,int l=1,int r=n,int rt=1){//有可能是先覆盖,再反转 
    pushdown(l,r,rt);
  	if(x<=l&&y>=r){
	  if(val==1){
	  	tr[rt].tag=val;tr[rt].rev=0;
	  	tr[rt].cnt[1]=tr[rt].q[1]=tr[rt].h[1]=tr[rt].mx[1]=r-l+1;
	  	tr[rt].cnt[0]=tr[rt].q[0]=tr[rt].h[0]=tr[rt].mx[0]=0;
	  }	
	  if(val==2){
	  	tr[rt].tag=val;tr[rt].rev=0;
	  	tr[rt].cnt[1]=tr[rt].q[1]=tr[rt].h[1]=tr[rt].mx[1]=0;
	  	tr[rt].cnt[0]=tr[rt].q[0]=tr[rt].h[0]=tr[rt].mx[0]=r-l+1;
	  }
	  if(val==3){
	  	tr[rt].rev^=1;
	  	swap(tr[rt].cnt[0],tr[rt].cnt[1]);
	  	swap(tr[rt].q[0],tr[rt].q[1]);
	  	swap(tr[rt].h[0],tr[rt].h[1]);
	  	swap(tr[rt].mx[0],tr[rt].mx[1]);
	  }
	  return ;
	}
	pushdown(l,r,rt);
	if(x<=mid) update(x,y,val,lson);
	if(y>mid) update(x,y,val,rson);
	pushup(rt);
  }
  inline int query_cnt(int x,int y,int l=1,int r=n,int rt=1){
  	if(x<=l&&y>=r) return tr[rt].cnt[1];
  	int res=0;
  	pushdown(l,r,rt);
  	if(x<=mid) res+=query_cnt(x,y,lson);
  	if(y>mid) res+=query_cnt(x,y,rson);
  	return res;
  }
  inline seg Max(seg x,seg y){
  	x.mx[1]=max(x.h[1]+y.q[1],max(x.mx[1],y.mx[1]));
  	if(y.h[1]==y.r-y.l+1) x.h[1]=x.h[1]+y.h[1];
	else x.h[1]=y.h[1];
	if(x.q[1]==x.r-x.l+1) x.q[1]+=y.q[1];
	x.r=y.r;
	return x;
  }
  inline seg query_mx(int x,int y,int l=1,int r=n,int rt=1){
  	if(x<=l&&y>=r) return tr[rt];
  	pushdown(l,r,rt);
  	if(x<=mid&&y>mid) return Max(query_mx(x,y,lson),query_mx(x,y,rson));	
  	else if(x<=mid) return query_mx(x,y,lson);
  	else if(y>mid) return query_mx(x,y,rson);
  }
}
using namespace SGT;

int main(){
  /*2023.7.4 H_W_Y P2572 [SCOI2010] 序列操作 线段树*/ 
  n=read();m=read();
  for(int i=1;i<=n;i++) a[i]=read();  
  build();
  for(int i=1;i<=m;i++){
  	op=read();l=read();r=read();
  	l++;r++;
  	if(op==0) update(l,r,2);
  	if(op==1) update(l,r,1);
  	if(op==2) update(l,r,3);
  	if(op==3) printf("%d\n",query_cnt(l,r));
  	if(op==4){
  	  seg ans=query_mx(l,r);
	  printf("%d\n",ans.mx[1]);	
	}
  }
  return 0;
}

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

题目大意

传送门
要求维护一个长度为 \(n\) 的字符串,要求支持三个操作:区间全部替换成 \(k\),区
\(k\) 个数,区间字典序排序。\(n, m \le 5 × 10^4\)

Solution

发现操作三其实是操作一和操作二结合起来实现
这样我们只需要维护操作一和操作二
暴力维护26棵线段树
也可以用珂朵莉树实现
线段树注意pushdown的写法
用到了mid就要传入参数l和r

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

const int maxn=1e5+10;
int n,m,op,x,y,ch[maxn],k,lst;
char s[maxn];
struct seg{
  int val,tag;
}tr[30][maxn*4];

inline int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

namespace SGT{
  int l,r;
  #define mid (l+r)/2
  #define lson l,mid,rt<<1
  #define rson mid+1,r,rt<<1|1
  #define lc rt<<1
  #define rc rt<<1|1
  inline void pushup(int p,int rt){tr[p][rt].val=tr[p][lc].val+tr[p][rc].val;}
  inline void pushdown(int p,int l,int r,int rt){
  	if(tr[p][rt].tag!=0){
  	  int t=tr[p][rt].tag;
  	  tr[p][rt<<1].tag=tr[p][rt<<1|1].tag=t;
  	  if(t==-1) tr[p][rt<<1].val=tr[p][rt<<1|1].val=0;
  	  else tr[p][rt<<1].val=mid-l+1,tr[p][rt<<1|1].val=r-mid;
	  tr[p][rt].tag=0;	
	}
  }
  inline void build(int p,int l=1,int r=n,int rt=1){
  	if(l==r){
  	  tr[p][rt].val=(ch[l]==p);
	  tr[p][rt].tag=0;
	  return;	
	}
	tr[p][rt].tag=0;
	build(p,lson);build(p,rson);
	pushup(p,rt);
  }
  inline void update(int p,int a,int b,int x,int l=1,int r=n,int rt=1){
  	if(a<=l&&b>=r){
  	  tr[p][rt].tag=x;
  	  if(x==-1) tr[p][rt].val=0;
	  else tr[p][rt].val=(r-l+1);
	  return;
	}
	pushdown(p,l,r,rt);
	if(a<=mid) update(p,a,b,x,lson);
	if(b>mid) update(p,a,b,x,rson);
	pushup(p,rt);
	
  }
  inline int query(int p,int a,int b,int l=1,int r=n,int rt=1){
  	if(a<=l&&b>=r) return tr[p][rt].val;
  	pushdown(p,l,r,rt);
  	int res=0;
  	if(a<=mid) res+=query(p,a,b,lson);
  	if(b>mid) res+=query(p,a,b,rson);
  	return res;
  }
}
using namespace SGT;

inline int change(char p){
  if(p>='A'&&p<='Z') return (p-'A')+1;
  return (p-'a')+1;
}

int main(){
  /*2023.7.4 H_W_Y P2787 语文1(chin1)- 理理思维 线段树*/ 
  n=read();m=read();
  scanf("%s",s+1);
  for(int i=1;i<=n;i++) ch[i]=change(s[i]);
  for(int i=1;i<=26;i++) build(i);
  for(int i=1;i<=m;i++){
    op=read();x=read();y=read();
  	if(op==1){
  	  scanf("%s",s);k=change(s[0]);
	  printf("%d\n",query(k,x,y));
	}
	if(op==2){
	  scanf("%s",s);k=change(s[0]);
	  for(int j=1;j<=26;j++) update(j,x,y,-1);
	  update(k,x,y,1);
	}
	if(op==3){
	  lst=x;
	  for(int j=1;j<=26;j++) ch[j]=query(j,x,y),update(j,x,y,-1);
	  for(int j=1;j<=26;j++)
	  	if(ch[j]>0) update(j,lst,lst+ch[j]-1,1),lst=lst+ch[j];
	}
  }
  return 0;
}