20230621-Segment Tree 1
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;
}
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种情况
首先,不难想到是线段树
维护每一个区间的最大值
注意两个条件:
- \(x\)是\(y\)年以来降雨量最多的,\(y+1 \sim x-1\)里都严格小于
- \(x\)年的降雨量不超过\(y\)
于是乎:
mx表示已知的年份中\(y+1 \sim x\)的最大降雨量
mx1表示已知的年份中\(y+1 \sim x-1\)的最大降雨量
(每一条都在前面几条不成立的条件下)
- \(x\)年不确定
- \(y\)年不确定 \(\to maybe\)
- \(y\)年确定
- \(mx \ge y \to false\)
- \(mx \lt y||y+1 \sim x中没有已知 \to maybe\)
- \(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;
}
P1712 [NOI2016] 区间
题目描述
传送门
简单易懂
Solution
注意到这个问题是有关最大和最小的
那么不难想到可以利用到单调性
也就是说我们先将线段长度按照升序排序
然后用两个指针从头扫到尾
每一次就加入一条线段
在线段树上判断是否有一个点被覆盖的次数 \(\ge m\)
如果有的话就从左开始缩小区间
以找到第一个最大覆盖次数 \(\lt m\)的位置
我们再继续加入下一条线段
线段树中维护的就是区间最大值
注意不要忘记判 \(-1\)!
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 N=1e6+5;
int ans=1e9,l,r,n,m,b[N],tr[N<<2],len,tag[N<<2];
struct node{
int l,r,len;
bool operator <(const node &rhs) const{
if(len!=rhs.len) return len<rhs.len;
return l<rhs.l;
}
}a[N];
void pushup(int rt){tr[rt]=max(tr[rt<<1],tr[rt<<1|1]);}
void pushdown(int rt){
if(tag[rt]){
tag[rt<<1]+=tag[rt];
tag[rt<<1|1]+=tag[rt];
tr[rt<<1]+=tag[rt];
tr[rt<<1|1]+=tag[rt];
tag[rt]=0;
}
}
void update(int l,int r,int rt,int a,int b,int x){
if(a<=l&&b>=r){
tr[rt]+=x;tag[rt]+=x;
return ;
}
pushdown(rt);
if(a<=mid) update(lson,a,b,x);
if(b>mid) update(rson,a,b,x);
pushup(rt);
}
int main(){
/*2023.7.27 H_W_Y P1712 [NOI2016] 区间 线段树*/
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i].l,&a[i].r);
b[i]=a[i].l;b[i+n]=a[i].r;
a[i].len=a[i].r-a[i].l;
}
sort(b+1,b+n*2+1);
len=unique(b+1,b+n*2+1)-b-1;
for(int i=1;i<=n;i++) a[i].l=lower_bound(b+1,b+len+1,a[i].l)-b,a[i].r=lower_bound(b+1,b+len+1,a[i].r)-b;
sort(a+1,a+n+1);
l=1;r=0;
while(l<=n&&r<n){
r++;update(1,len,1,a[r].l,a[r].r,1);
while(tr[1]>=m){
ans=min(ans,a[r].len-a[l].len);
update(1,len,1,a[l].l,a[l].r,-1);
l++;
}
}
if(ans==1e9) printf("-1\n");
else printf("%d\n",ans);
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]\)就有三个来源:
- \(sum1[j]=sum1[i]\)
- \(sum2[j]=sum2[i]\)
- \(|(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
一道线段树的裸题(好久没遇到了)
但是为什么是紫题
很恶心的线段树,特别考验心态……
注意反转标记和覆盖标记的顺序
- 覆盖标记在反转标记之上
那么可以把反转标记清零 - 反转标记在覆盖标记之上
那么可以对覆盖标记直接修改
注意在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;
}