线段树
线段树的基础知识
线段树概念
线段树是基于分治思想的二叉树,维护区间信息,在logn的时间执行区间修改和区间查询。可维护的区间信息包括区间和,区间最值,区间gcd等等。
线段树的创建
p是二叉树的下标,下标从1开始,l,r分别表示所维护区间的左右端点(w数组的左右区间下标),sum表示当前节点维护区间的总和。叶子节点存放元素本身,非叶子节点存储区间内元素统计值。
w[10]={5,2,0,1,9,7,2,1,2,3}
流程:
1.赋值节点p
2.如果是叶子节点返回,否则裂开进入左右子树。
3.更新需要维护的区间和,左右子树区间和相加即可
线段树空间
开4*N
点修改
区间修改
区间查询
总体函数
参考:董晓算法
讲得真的非常棒,宝藏up。
练习
P3374
#include<bits/stdc++.h>
using namespace std;
#define lc p<<1
#define rc p<<1|1
#define debug(x) cout<<#x<<" = "<<x<<endl
const int N=5e5+10;
typedef long long ll;
int n,m,q,x,y,w[N];
struct tree{
int l,r,sum;
}tr[4*N];
void pushup(int p){
tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void build(int p,int l,int r){
tr[p]={l,r,w[l]};
if(l==r) return;
int m=l+r>>1;
build(lc,l,m);
build(rc,m+1,r);
pushup(p);
return;
}
void update(int p,int x,int k){
// debug(p);
if(tr[p].l==x&&tr[p].r==x){
tr[p].sum+=k;
return;
}
int m=tr[p].l+tr[p].r>>1;
// debug(m);
if(x<=m) update(lc,x,k);
if(x>m) update(rc,x,k);//这里不是y,点修改不是区间修改
pushup(p);
}
int query(int p,int x,int y){
if(x<=tr[p].l&&tr[p].r<=y){
return tr[p].sum;
}
int m=tr[p].l+tr[p].r>>1;
int sum=0;
if(x<=m) sum+=query(lc,x,y);
if(y>m) sum+=query(rc,x,y);
return sum;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i];
build(1,1,n);
while(m--){
cin>>q>>x>>y;
if(q==1){
update(1,x,y);
// debug(q);
}else{
cout<<query(1,x,y)<<endl;
}
}
return 0;
}
P3372
#include<bits/stdc++.h>
using namespace std;
#define lc p<<1
#define rc p<<1|1
typedef long long ll;
const int N=1e5+10;
int n,m,q,x,y,k;
ll w[N];
struct tree{
int l,r,add;
ll sum;
}tr[4*N];
void pushup(int p){
tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void pushdown(int p){
if(tr[p].add){
tr[lc].sum+=(tr[lc].r-tr[lc].l+1)*tr[p].add;//加等于
tr[rc].sum+=(tr[rc].r-tr[rc].l+1)*tr[p].add;
tr[lc].add+=tr[p].add;
tr[rc].add+=tr[p].add;
tr[p].add=0;
}
}
void build(int p,int l,int r){
tr[p]={l,r,0,w[l]};
if(l==r) return;
int m=l+r>>1;
build(lc,l,m);
build(rc,m+1,r);
pushup(p);
}
//修改和查询都是先判断当前节点区间是否被(x,y)覆盖了
//覆盖了进行操作后直接返回
//没覆盖裂开,向下展开懒标记,然后根据条件进入左右子树,最后回到当前节点进行操作后离开。
void update(int p,int x,int y,int k){
if(x<=tr[p].l&&tr[p].r<=y){
tr[p].sum+=(tr[p].r-tr[p].l+1)*k;
tr[p].add+=k;
return;
}
int m=tr[p].r+tr[p].l>>1;
pushdown(p);
if(x<=m) update(lc,x,y,k);
if(y>m) update(rc,x,y,k);
pushup(p);
}
ll query(int p,int x,int y){
if(x<=tr[p].l&&tr[p].r<=y){
return tr[p].sum;
}
int m=tr[p].r+tr[p].l>>1;
pushdown(p);
ll sum=0;
if(x<=m) sum+=query(lc,x,y);
if(y>m) sum+=query(rc,x,y);
return sum;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i];
build(1,1,n);
while(m--){
cin>>q>>x>>y;
if(q==1){
cin>>k;
update(1,x,y,k);
}else{
cout<<query(1,x,y)<<endl;
}
}
return 0;
}
总结:
1.一共五个函数。build(p,l,r),pushup(int p),pushdown(int p),update(p,x,y,k),query(p,x,y)。pushdown和update有联系,update不用裂开的时候按照条件更改当前节点权值,pushdown更改左右子节点的权值,相同的操作。
2.
//修改和查询都是先判断当前节点区间是否被(x,y)覆盖了
//覆盖了进行操作后直接返回
//没覆盖裂开,向下展开懒标记,然后根据条件进入左右子树,最后回到当前节点进行操作后离开。
P3386
#include<bits/stdc++.h>
using namespace std;
#define lc p<<1
#define rc p<<1|1
typedef long long ll;
const int N=5e5+10;
int n,m,q,x,y,k;
ll w[N];
struct tree{
int l,r,add;
ll sum;
}tr[4*N];
void pushup(int p){
tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void pushdown(int p){
if(tr[p].add){
tr[lc].sum+=(tr[lc].r-tr[lc].l+1)*tr[p].add;//加等于
tr[rc].sum+=(tr[rc].r-tr[rc].l+1)*tr[p].add;
tr[lc].add+=tr[p].add;
tr[rc].add+=tr[p].add;
tr[p].add=0;
}
}
void build(int p,int l,int r){
tr[p]={l,r,0,w[l]};
if(l==r) return;
int m=l+r>>1;
build(lc,l,m);
build(rc,m+1,r);
pushup(p);
}
//修改和查询都是先判断当前节点区间是否被(x,y)覆盖了
//覆盖了进行操作后直接返回
//没覆盖裂开,向下展开懒标记,然后根据条件进入左右子树,最后回到当前节点进行操作后离开。
void update(int p,int x,int y,int k){
if(x<=tr[p].l&&tr[p].r<=y){
tr[p].sum+=(tr[p].r-tr[p].l+1)*k;
tr[p].add+=k;
return;
}
int m=tr[p].r+tr[p].l>>1;
pushdown(p);
if(x<=m) update(lc,x,y,k);
if(y>m) update(rc,x,y,k);
pushup(p);
}
ll query(int p,int x,int y){
if(x<=tr[p].l&&tr[p].r<=y){
return tr[p].sum;
}
int m=tr[p].r+tr[p].l>>1;
pushdown(p);
ll sum=0;
if(x<=m) sum+=query(lc,x,y);
if(y>m) sum+=query(rc,x,y);
return sum;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i];
build(1,1,n);
while(m--){
cin>>q;
if(q==1){
cin>>x>>y>>k;
update(1,x,y,k);
}else{
cin>>x;
cout<<query(1,x,x)<<endl;
}
}
return 0;
}
总结:上面三个题目都是一样的,学会区间修改,区间查询,三道题就是一道题。
P1816
分析:此题的维护的权值为区间内的最小值。
#include<bits/stdc++.h>
using namespace std;
#define lc p<<1
#define rc p<<1|1
typedef long long ll;
const int N=5e5+10;
int n,m,q,x,y,k;
ll w[N];
struct tree{
int l,r,add;
ll sum;//表示区间(l,r)内最小值
}tr[4*N];
void pushup(int p){
tr[p].sum=min(tr[lc].sum,tr[rc].sum);
}
void pushdown(int p){
if(tr[p].add){
tr[lc].sum+=tr[p].add;
tr[rc].sum+=tr[p].add;
tr[lc].add+=tr[p].add;
tr[rc].add+=tr[p].add;
tr[p].add=0;
}
}
void build(int p,int l,int r){
tr[p]={l,r,0,w[l]};
if(l==r) return;
int m=l+r>>1;
build(lc,l,m);
build(rc,m+1,r);
pushup(p);
}
//修改和查询都是先判断当前节点区间是否被(x,y)覆盖了
//覆盖了进行操作后直接返回
//没覆盖裂开,向下展开懒标记,然后根据条件进入左右子树,最后回到当前节点进行操作后离开。
void update(int p,int x,int y,int k){
if(x<=tr[p].l&&tr[p].r<=y){
tr[p].sum+=k;
tr[p].add+=k;
return;
}
int m=tr[p].r+tr[p].l>>1;
pushdown(p);
if(x<=m) update(lc,x,y,k);
if(y>m) update(rc,x,y,k);
pushup(p);
}
ll query(int p,int x,int y){
if(x<=tr[p].l&&tr[p].r<=y){
return tr[p].sum;
}
int m=tr[p].r+tr[p].l>>1;
pushdown(p);
ll sum=1e15;
if(x<=m) sum=min(sum,query(lc,x,y));
if(y>m) sum=min(sum,query(rc,x,y));
return sum;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i];
build(1,1,n);
while(m--){
cin>>x>>y;
cout<<query(1,x,y)<<" ";
}
return 0;
}
P2574
分析:本题维护的权值为区间内1的个数。update修改的操作为将1变为0,将0变为1,与1异或。
#include<bits/stdc++.h>
using namespace std;
#define lc p<<1
#define rc p<<1|1
typedef long long ll;
const int N=5e5+10;
int n,m,q,x,y,k;
ll w[N];
struct tree{
int l,r,add;
ll sum;//表示区间(l,r)内1的个数
}tr[4*N];
void pushup(int p){
tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void pushdown(int p){
if(tr[p].add){
tr[lc].sum=(tr[lc].r-tr[lc].l+1-tr[lc].sum);//将1变成0,将0变成1
tr[rc].sum=(tr[rc].r-tr[rc].l+1-tr[rc].sum);
tr[lc].add^=1;
tr[rc].add^=1;
tr[p].add=0;
}
}
void build(int p,int l,int r){
tr[p]={l,r,0,w[l]};
if(l==r) return;
int m=l+r>>1;
build(lc,l,m);
build(rc,m+1,r);
pushup(p);
}
//修改和查询都是先判断当前节点区间是否被(x,y)覆盖了
//覆盖了进行操作后直接返回
//没覆盖裂开,向下展开懒标记,然后根据条件进入左右子树,最后回到当前节点进行操作后离开。
void update(int p,int x,int y){
if(x<=tr[p].l&&tr[p].r<=y){
tr[p].sum=(tr[p].r-tr[p].l+1-tr[p].sum);//这里update的操作和pushdow的操作一样
tr[p].add^=1;
return;
}
int m=tr[p].r+tr[p].l>>1;
pushdown(p);
if(x<=m) update(lc,x,y);
if(y>m) update(rc,x,y);
pushup(p);
}
ll query(int p,int x,int y){
if(x<=tr[p].l&&tr[p].r<=y){
return tr[p].sum;
}
int m=tr[p].r+tr[p].l>>1;
pushdown(p);
ll sum=0;
if(x<=m) sum+=query(lc,x,y);
if(y>m) sum+=query(rc,x,y);
return sum;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) scanf("%1d",&w[i]);//注意输入技巧
build(1,1,n);
while(m--){
cin>>q>>x>>y;
if(q==0){
update(1,x,y);
}else{
cout<<query(1,x,y)<<endl;
}
}
return 0;
}
P2846
和上一题一样,关着的灯表示状态0,开着的灯表示状态1,求区间内开着的灯的数量。
#include<bits/stdc++.h>
using namespace std;
#define lc p<<1
#define rc p<<1|1
typedef long long ll;
const int N=5e5+10;
int n,m,q,x,y,k;
ll w[N];
struct tree{
int l,r,add;
ll sum;//表示区间(l,r)内1的个数
}tr[4*N];
void pushup(int p){
tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void pushdown(int p){
if(tr[p].add){
tr[lc].sum=(tr[lc].r-tr[lc].l+1-tr[lc].sum);//将1变成0,将0变成1
tr[rc].sum=(tr[rc].r-tr[rc].l+1-tr[rc].sum);
tr[lc].add^=1;
tr[rc].add^=1;
tr[p].add=0;
}
}
void build(int p,int l,int r){
tr[p]={l,r,0,w[l]};
if(l==r) return;
int m=l+r>>1;
build(lc,l,m);
build(rc,m+1,r);
pushup(p);
}
//修改和查询都是先判断当前节点区间是否被(x,y)覆盖了
//覆盖了进行操作后直接返回
//没覆盖裂开,向下展开懒标记,然后根据条件进入左右子树,最后回到当前节点进行操作后离开。
void update(int p,int x,int y){
if(x<=tr[p].l&&tr[p].r<=y){
tr[p].sum=(tr[p].r-tr[p].l+1-tr[p].sum);//这里update的操作和pushdow的操作一样
tr[p].add^=1;
return;
}
int m=tr[p].r+tr[p].l>>1;
pushdown(p);
if(x<=m) update(lc,x,y);
if(y>m) update(rc,x,y);
pushup(p);
}
ll query(int p,int x,int y){
if(x<=tr[p].l&&tr[p].r<=y){
return tr[p].sum;
}
int m=tr[p].r+tr[p].l>>1;
pushdown(p);
ll sum=0;
if(x<=m) sum+=query(lc,x,y);
if(y>m) sum+=query(rc,x,y);
return sum;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) w[i]=0;
build(1,1,n);
while(m--){
cin>>q>>x>>y;
if(q==0){
update(1,x,y);
}else{
cout<<query(1,x,y)<<endl;
}
}
return 0;
}
P1471
技巧
#include<bits/stdc++.h>
using namespace std;
#define lc p<<1
#define rc p<<1|1
#define debug(x) cout<<#x<<" = "<<x<<endl
//#define int double//double类型不能当下标
typedef long long ll;
const int N=1e5+10;
int n,m,q,x,y;
double k;
double w[N];
struct tree{
int l,r;
double add,sum,sum2;//sum表示区间内数之和,sum2表示区间内数平方和
}tr[4*N];
void pushup(int p){
tr[p].sum=tr[lc].sum+tr[rc].sum;
tr[p].sum2=tr[lc].sum2+tr[rc].sum2;
}
void pushdown(int p){//相当于在左右子树进行操作,操作数为k
if(tr[p].add){
//要先更新sum2,不然先更新sum,会影响sum2的更新,tr[lc].sum tr[rc].sum
tr[lc].sum2+=(2.0*tr[p].add*tr[lc].sum+(tr[lc].r-tr[lc].l+1)*tr[p].add*tr[p].add);
tr[rc].sum2+=(2.0*tr[p].add*tr[rc].sum+(tr[rc].r-tr[rc].l+1)*tr[p].add*tr[p].add);
tr[lc].sum+=(tr[lc].r-tr[lc].l+1)*tr[p].add;//加等于
tr[rc].sum+=(tr[rc].r-tr[rc].l+1)*tr[p].add;
tr[lc].add+=tr[p].add;
tr[rc].add+=tr[p].add;
tr[p].add=0;
}
}
void build(int p,int l,int r){
tr[p]={l,r,0,w[l],w[l]*w[l]};
if(l==r) return;
int m=l+r>>1;
build(lc,l,m);
build(rc,m+1,r);
pushup(p);
}
//修改和查询都是先判断当前节点区间是否被(x,y)覆盖了
//覆盖了进行操作后直接返回
//没覆盖裂开,向下展开懒标记,然后根据条件进入左右子树,最后回到当前节点进行操作后离开。
void update(int p,int x,int y,double k){//此处参数为double
if(x<=tr[p].l&&tr[p].r<=y){
//要先更新sum2,不然先更新sum,会影响sum2的更新,tr[lc].sum tr[rc].sum
tr[p].sum2+=(2.0*k*tr[p].sum+(tr[p].r-tr[p].l+1)*k*k);
tr[p].sum+=(tr[p].r-tr[p].l+1)*k;
tr[p].add+=k;
return;
}
// debug(p);
int m=tr[p].r+tr[p].l>>1;
// debug(m);
pushdown(p);
if(x<=m) update(lc,x,y,k);
if(y>m) update(rc,x,y,k);
pushup(p);
// debug(tr[p].sum);
// debug(tr[p].sum2);
// cout<<endl;
}
double query(int p,int x,int y){//返回区间数和
if(x<=tr[p].l&&tr[p].r<=y){
return tr[p].sum;
}
int m=tr[p].r+tr[p].l>>1;
pushdown(p);
double sum=0;
if(x<=m) sum+=query(lc,x,y);
if(y>m) sum+=query(rc,x,y);
return sum;
}
double query2(int p,int x,int y){//返回区间数平方和,注意是int类型的,不然全寄了
if(x<=tr[p].l&&tr[p].r<=y){
return tr[p].sum2;
}
int m=tr[p].r+tr[p].l>>1;
pushdown(p);
double sum=0;
if(x<=m) sum+=query2(lc,x,y);
if(y>m) sum+=query2(rc,x,y);
return sum;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i];
build(1,1,n);
while(m--){
cin>>q>>x>>y;
if(q==1){
cin>>k;
update(1,x,y,k);
}else if(q==2){
double ans=1.0*query(1,x,y)/(y-x+1);
printf("%.4lf\n",ans);
}else{
double ans=1.0*query2(1,x,y)/(y-x+1)-1.0*query(1,x,y)/(y-x+1)*query(1,x,y)/(y-x+1);
printf("%.4lf\n",ans);
}
}
return 0;
}
注意需要先更新sum2再更新sum,因为sum2需要用到sum,先更新sum会导致值变化从而使得sum2求错了。另外注意精度,query函数返回的类型应该是double类型,不然都寄了。
CF527C
线段树解法:求一段区间内最长相邻1的个数,lmx,rmx,mx分别表示这段区间左边最长,右边最长,区间最长连续1的个数。1表示没被砍,0表示被砍,因为区间权值代表的是长度,所以在每个单元中间再插入一个单元,表示被砍的单元。所以真实最长为(mx+1)/2。
#include<bits/stdc++.h>
#define ll long long
#define pl p<<1
#define pr p<<1|1
using namespace std;
int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
const int N=4e5+10;
int w,h,n,x;
char op;
struct Tree{
int l,r;
int lmx,rmx,mx;
}s[2][4*N];
void pushup(int k,int p){
s[k][p].lmx=s[k][pl].lmx;
if(s[k][pl].lmx==s[k][pl].r-s[k][pl].l+1)
s[k][p].lmx=max(s[k][p].lmx,s[k][pl].lmx+s[k][pr].lmx);
s[k][p].rmx=s[k][pr].rmx;
if(s[k][pr].rmx==s[k][pr].r-s[k][pr].l+1)
s[k][p].rmx=max(s[k][p].rmx,s[k][pr].rmx+s[k][pl].rmx);
s[k][p].mx=max(max(s[k][pl].mx,s[k][pr].mx),max(s[k][p].lmx,s[k][p].rmx));
s[k][p].mx=max(s[k][p].mx,s[k][pl].rmx+s[k][pr].lmx);
}
void build(int k,int p,int l,int r){
s[k][p].l=l;s[k][p].r=r;
if(l==r){
s[k][p].mx=s[k][p].lmx=s[k][p].rmx=1;
return;
}
int mid=(l+r)>>1;
build(k,pl,l,mid);
build(k,pr,mid+1,r);
pushup(k,p);
}
void change(int k,int p,int x){
if(s[k][p].l==s[k][p].r){
s[k][p].mx=s[k][p].lmx=s[k][p].rmx=0;
return;
}
int mid=(s[k][p].l+s[k][p].r)>>1;
if(x<=mid)
change(k,pl,x);
else
change(k,pr,x);
pushup(k,p);
}
ll ask(){
return (ll)(s[0][1].mx+1)/2*(s[1][1].mx+1)/2;
}
int main()
{
w=read();h=read();n=read();
build(0,1,1,2*w-1);
build(1,1,1,2*h-1);
for(int i=1;i<=n;i++){
cin>>op;
x=read();
if(op=='H')
change(1,1,2*x);
else
change(0,1,2*x);
cout<<ask()<<endl;
}
return 0;
}
set,multiset解法:
#include <bits/stdc++.h>
#define int long long
using namespace std;
int w,h,n;
multiset<int> x,y,lx,ly;//存储所有碎片的边长、切割的位置
multiset<int>::iterator it;
signed main()
{
cin >> w >> h >> n;//最开始是一整块玻璃
x.insert(w);
y.insert(h);
lx.insert(0);
lx.insert(w);
ly.insert(0);
ly.insert(h);
while(n--)
{
char ch;
int pos;
cin >> ch >> pos;
if (ch=='H')
{
int l,r;
ly.insert(pos);
it=ly.find(pos);
it--;//找到pos前一个切割位置
l=*it;
it++;
it++;//找到pos后一个切割位置
r=*it;//删除一个高度r-l,增加两个新高度
it=y.find(r-l);
y.erase(it);
y.insert(r-pos);
y.insert(pos-l);
}
else
{
int l,r;
lx.insert(pos);
it=lx.find(pos);
it--;//找到pos前一个切割位置
l=*it;
it++;
it++;//找到pos后一个切割位置
r=*it;//删除一个宽度r-l,增加两个新宽度
it=x.find(r-l);
x.erase(it);
x.insert(r-pos);
x.insert(pos-l);
}
int r,c;
it=x.end();
it--;
r=*it;
it=y.end();
it--;
c=*it;
cout << r*c << "\n";
}
return 0;
}
P2894
还是一样的,和上题类似,比上题还简单一点。空房子标记为1,住人房子标记为0,求连续空房子的个数,即连续1的个数。
#include <cstdio>
#define ls x << 1
#define rs x << 1 | 1
#define INF 0x3f3f3f3f
const int MAXN = 50000;
int n,m;
struct node{
int l , r , f;
int num , lnum , rnum;
}Tree[ 4 * MAXN + 5 ];
int Max( int x , int y ) {
return x > y ? x : y;
}
int Min( int x , int y ) {
return x < y ? x : y;
}
void Build( int x , int l , int r ) {
Tree[ x ].l = l , Tree[ x ].r = r , Tree[ x ].f = 2;
Tree[ x ].lnum = Tree[ x ].rnum = Tree[ x ].num = Tree[ x ].r - Tree[ x ].l + 1;
if( l == r ) return;
int Mid = ( l + r ) / 2;
Build( ls , l , Mid );
Build( rs , Mid + 1 , r );
}
void pushup( int x ) {
Tree[ x ].lnum = Tree[ ls ].lnum;
if( Tree[ ls ].num == Tree[ ls ].r - Tree[ ls ].l + 1 ) Tree[ x ].lnum = Tree[ ls ].num + Tree[ rs ].lnum;
Tree[ x ].rnum = Tree[ rs ].rnum;
if( Tree[ rs ].num == Tree[ rs ].r - Tree[ rs ].l + 1 ) Tree[ x ].rnum = Tree[ rs ].num + Tree[ ls ].rnum;
Tree[ x ].num = Max( Max( Tree[ ls ].num , Tree[ rs ].num ) , Tree[ ls ].rnum + Tree[ rs ].lnum );
}
void pushdown( int x ) {
// if( Tree[ x ].l == Tree[ x ].r )
// return;
if( Tree[ x ].f == 0 ) {
Tree[ ls ].num = 0 , Tree[ ls ].f = 0;
Tree[ rs ].num = 0 , Tree[ rs ].f = 0;
Tree[ ls ].lnum = Tree[ ls ].rnum = 0;
Tree[ rs ].lnum = Tree[ rs ].rnum = 0;
Tree[ x ].f = 2;
}
if( Tree[ x ].f == 1 ) {
Tree[ ls ].num = Tree[ ls ].r - Tree[ ls ].l + 1 , Tree[ ls ].f = 1;
Tree[ rs ].num = Tree[ rs ].r - Tree[ rs ].l + 1 , Tree[ rs ].f = 1;
Tree[ ls ].lnum = Tree[ ls ].rnum = Tree[ ls ].num;
Tree[ rs ].lnum = Tree[ rs ].rnum = Tree[ rs ].num;
Tree[ x ].f = 2;
}
}
void Insert( int x , int l , int r , int k ) {
if( l > Tree[ x ].r || Tree[ x ].l > r )
return;
if( l <= Tree[ x ].l && Tree[ x ].r <= r ) {
Tree[ x ].f = k;
Tree[ x ].num = k == 1 ? Tree[ x ].r - Tree[ x ].l + 1 : 0;
Tree[ x ].lnum = Tree[ x ].rnum = Tree[ x ].num;
return;
}
pushdown( x );
Insert( ls , l , r , k );
Insert( rs , l , r , k );
pushup( x );
}
int Find( int x , int k ) {
if( Tree[ x ].l == Tree[ x ].r ) return Tree[ x ].l;
pushdown( x );
if( Tree[ ls ].num >= k ) return Find( ls , k );
if( Tree[ ls ].rnum + Tree[ rs ].lnum >= k ) return Tree[ ls ].r - Tree[ ls ].rnum + 1;
if( Tree[ rs ].num >= k ) return Find( rs , k );
}
int main( ) {
//freopen("hotel.in","r",stdin);
//freopen("hotel.out","w",stdout);
scanf("%d %d",&n,&m);
Build( 1 , 1 , n );
int op,x,d;
for( int i = 1 ; i <= m ; i ++ ) {
scanf("%d",&op);
if( op == 1 ) {
scanf("%d",&d);
if( Tree[ 1 ].num < d ) {
printf("0\n");
continue;
}
int r = Find( 1 , d );
Insert( 1 , r , r + d - 1 , 0 );
printf("%d\n",r);
}
else if( op == 2 ) {
scanf("%d %d",&x,&d);
Insert( 1 , x , x + d - 1 , 1 );//
}
}
return 0;
}
天梯L3-017森森快递
思路:线段树维持的属性是区间内最小值,每次查询区间最小值,答案加上最小值,然后修改这段区间,让这段区间内的所有数都减去最小值。注意还要配合贪心。 即根据右端点从小到大排序,右端点相同的情况,根据左端点从大到小排序。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
#define lc p<<1
#define rc p<<1|1
#define debug(x) cout<<#x<<" = "<<x<<endl
#define int long long
int n,q,w[N],x;
struct node{
int l,r,var,add;
}tr[4*N];
struct eg{
int l,r;
bool operator<(const eg& w) const{
if(r!=w.r) return r<w.r;
return l>w.l;
}
}e[N];
void pushup(int p){
tr[p].var=min(tr[lc].var,tr[rc].var);
}
void pushdown(int p){
if(tr[p].add){
tr[lc].var+=tr[p].add;
tr[rc].var+=tr[p].add;
tr[lc].add+=tr[p].add;
tr[rc].add+=tr[p].add;
tr[p].add=0;
}
}
void build(int p,int l,int r){
tr[p]={l,r,w[l],0};
if(l==r) return;
int m=l+r>>1;
build(lc,l,m);
build(rc,m+1,r);
pushup(p);
}
int query(int p,int x,int y){
if(x<=tr[p].l&&tr[p].r<=y){
return tr[p].var;
}
int m=tr[p].l+tr[p].r>>1;
pushdown(p);
int ans=0x3f3f3f3f3f3f3f3f;//这个要初始化64位,不然最后一个测试点过不去
if(x<=m) ans=min(ans,query(lc,x,y));
if(y>m) ans=min(ans,query(rc,x,y));
return ans;
}
void update(int p,int x,int y,int k){
if(x<=tr[p].l&&tr[p].r<=y){
tr[p].var+=k;
tr[p].add+=k;
return;
}
int m=tr[p].l+tr[p].r>>1;
pushdown(p);
if(x<=m) update(lc,x,y,k);
if(y>m) update(rc,x,y,k);
pushup(p);
}
signed main(){
cin>>n>>q;
for(int i=1;i<=n-1;i++) cin>>w[i];
build(1,1,n-1);
for(int i=1;i<=q;i++){
int l,r;cin>>l>>r;
if(l>r) swap(l,r);//大小顺序
e[i]={l,r};
}
sort(e+1,e+q+1);
int ans=0;
for(int i=1;i<=q;i++){
int l=e[i].l+1,r=e[i].r;//l注意要加1,因为建树区间是从1~n-1
int num=query(1,l,r);
if(num>0){
ans+=num;
update(1,l,r,-num);
}
}
cout<<ans<<endl;
return 0;
}