线段树模板
//线段树一般用来解决可合并的区间问题
//树上维护子树区间的问题也可以通过dfs序+线段树去维护
//不要质疑线段树写法的正确性,首先检查做法是否正确
//不要忘记build,dfs序问题build时注意下标
//数组记得开4倍,push_down一定要想好顺序
//对于每个问题,我们只要考虑清楚push_down和push_up的写法即可
//当问题不好处理的时候,想象一下对于单次询问暴力的做法,再去想区间合并操作
//对于求一个区间内权值大于y的最小下标x时,就先看左区间是不是能找到这样的值,找不到再找右区间
//若找不到返回-1否则返回下标,用区间max<=y进行剪枝防止复杂度退化
//TLE有可能是数据溢出的问题,检查函数传参的变量类型(是否需要long long)
//TLE不要怀疑线段树的复杂度,要想想有没有优化或者哪里是不是死循环
//多个update或者query检查是否调用错误
//如果区间赋值对子区间的改变有多次的影响,要把add数组更改带来的变化存在另一个lazy数组
struct T
{
int l,r,mid;
int add,sum;
}tree[maxn<<2];
void push_up(int rt)
{
//在这里合并子区间
}
void push_down(int rt)
{
if(tree[rt].add)
{
//在这里下传标记
tree[rt].add=0;
}
}
void build(int rt,int l,int r)
{
tree[rt].add=0;
tree[rt].l=l;
tree[rt].r=r;
if(l==r)
{
tree[rt].sum=a[l];
return ;
}
int mid=tree[rt].mid=l+r>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
push_up(rt);
}
void update(int rt,int l,int r)
{
if(tree[rt].r<l||tree[rt].l>r) return ;
if(tree[rt].l>=l&&tree[rt].r<=r)
{
//更新区间答案,并更新lazy数组
return ;
}
push_down(rt);
if(tree[rt].mid>=l) update(rt<<1,l,r);
if(tree[rt].mid<r) update(rt<<1|1,l,r);
push_up(rt);
}
int query(int rt,int l,int r)
{
if(tree[rt].r<l||tree[rt].l>r) return 0;
if(tree[rt].l>=l&&tree[rt].r<=r) // 返回本区间答案。
push_down(rt);
int ans=0;
if(tree[rt].mid>=l) ans+=query(rt<<1,l,r);
if(tree[rt].mid<r) ans+=query(rt<<1|1,l,r);
push_up(rt);
return ans;
}
线段树第一题
链接:
Codeforces Round #442 (Div. 2)E. Danil and a Part-time Job
题意:
给你一颗有根树,树上每一个节点有一个灯,现在要支持两种操作,第一种操作是统计一颗子树内开着的灯个数。第二种操作是将一个子树内的所有灯状态改变(开灯->关灯,关灯->开灯)。
做法:
首先对于树的dfs序建立线段树,这样每一棵子树都能表示成一段区间,之后问题就转换为,区间改变01状态,区间求和。
这个问题我们可以通过lazy数组实现,首先我们统计出最初状态下每棵子树内灯的状况,将每棵子树内亮着的灯的个数表示为
s
u
m
sum
sum,对于区间更新,我们只需要用一个
a
d
d
add
add表示这个区间是否被翻转,
a
d
d
add
add每次和
1
1
1进行异或,这表示如果偶数次翻转,区间就会回到最初的情况,之后我们只需要知道这个区间是否被翻转也就可以更新
s
u
m
sum
sum。
对于 p u s h _ d o w n push \_ down push_down函数,我们首先要更新子区间的 a d d add add数组 ( 0 − > 1 , 1 − > 0 ) (0->1,1->0) (0−>1,1−>0),之后更新子区间的 s u m sum sum,这里我们要知道子区间内一共点的个数,也就是 ( r − l + 1 ) (r-l+1) (r−l+1),之后再更新 s u m sum sum即可。
对于 p u s h _ u p push\_up push_up函数,只需要合并子区间的和即可。
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn = 2e5+5;
int tim;
int t[maxn];
int sum[maxn],sum2[maxn];
int in[maxn],ou[maxn],dfn[maxn];
vector<int> G[maxn];
void dfs(int rt,int fa)
{
in[rt]=++tim;
dfn[tim]=rt;
for(int i=0;i<G[rt].size();i++)
{
int to=G[rt][i];
if(to==fa) continue;
dfs(to,rt);
}
ou[rt]=tim;
}
struct T
{
int l,r,mid;
int add,sum;
}tree[maxn<<2];
void push_up(int rt)
{
tree[rt].sum=tree[rt<<1].sum+tree[rt<<1|1].sum;
}
void push_down(int rt)
{
if(tree[rt].add)
{
tree[rt<<1].add^=1;
tree[rt<<1].sum=(tree[rt<<1].r-tree[rt<<1].l+1)-tree[rt<<1].sum;
tree[rt<<1|1].add^=1;
tree[rt<<1|1].sum=(tree[rt<<1|1].r-tree[rt<<1|1].l+1)-tree[rt<<1|1].sum;
tree[rt].add=0;
}
}
void build(int rt,int l,int r)
{
tree[rt].add=0;
tree[rt].l=l;
tree[rt].r=r;
if(l==r)
{
tree[rt].sum=t[dfn[l]];//dfs序的问题这里记得dfn
return ;
}
int mid=tree[rt].mid=l+r>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
push_up(rt);
}
void update(int rt,int l,int r)
{
if(tree[rt].r<l||tree[rt].l>r) return ;
if(tree[rt].l>=l&&tree[rt].r<=r)
{
tree[rt].sum=(tree[rt].r-tree[rt].l+1)-tree[rt].sum;
tree[rt].add^=1;
return ;
}
push_down(rt);
if(tree[rt].mid>=l) update(rt<<1,l,r);
if(tree[rt].mid<r) update(rt<<1|1,l,r);
push_up(rt);
}
int query(int rt,int l,int r)
{
if(tree[rt].r<l||tree[rt].l>r) return 0;
if(tree[rt].l>=l&&tree[rt].r<=r) return tree[rt].sum;
push_down(rt);
int ans=0;
if(tree[rt].mid>=l) ans+=query(rt<<1,l,r);
if(tree[rt].mid<r) ans+=query(rt<<1|1,l,r);
push_up(rt);
return ans;
}
int main()
{
int n,x;
scanf("%d",&n);
for(int i=2;i<=n;i++)
{
scanf("%d",&x);
G[x].push_back(i);
G[i].push_back(x);
}
for(int i=1;i<=n;i++) scanf("%d",&t[i]);
dfs(1,-1);//获取dfs序
build(1,1,n);
int q;
scanf("%d",&q);
while(q--)
{
char op[4];
int v;
scanf("%s%d",op,&v);
if(op[0]=='p') update(1,in[v],ou[v]);
else printf("%d\n",query(1,in[v],ou[v]));
}
return 0;
}
线段树第二题
链接
Codecraft-18 and Codeforces Round #458 (Div. 1 + Div. 2, combined)
题意
给你一个区间,要支持两种区间操作。
第一种操作是单点更新,第二种操作是询问某个区间是否可以去掉一个元素使这段区间的
g
c
d
gcd
gcd为
x
x
x的倍数。
做法
首先先要支持区间
g
c
d
gcd
gcd和单点更新,这个用普通的线段树就可以维护。
之后对于每个操作
2
2
2,我们先查询当前区间的
g
c
d
gcd
gcd是否为
x
x
x的倍数,若当前
g
c
d
gcd
gcd为
x
x
x的倍数,则随意去掉哪个元素即可。
否则查询左右区间的
g
c
d
gcd
gcd,如果两个子区间的
g
c
d
gcd
gcd都不是
x
x
x的倍数,则至少删除两个元素,所以直接返回
f
a
l
s
e
false
false。
如果其中一个是
x
x
x的倍数,则问题转换为一个子问题,只需要查询另一个子区间去掉一个元素
g
c
d
gcd
gcd能否是
x
x
x的倍数即可。
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 5e5+5;
int a[maxn];
struct T
{
int l,r,mid;
int GCD;
}tree[maxn<<2];
void push_up(int rt)
{
tree[rt].GCD=__gcd(tree[rt<<1].GCD,tree[rt<<1|1].GCD);
}
void build(int rt,int l,int r)
{
tree[rt].l=l;
tree[rt].r=r;
if(l==r)
{
tree[rt].GCD=a[l];
return ;
}
int mid=tree[rt].mid=l+r>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
push_up(rt);
}
void update(int rt,int pos,int val)
{
if(tree[rt].l==tree[rt].r)
{
tree[rt].GCD=val;
return ;
}
if(pos<=tree[rt].mid) update(rt<<1,pos,val);
else update(rt<<1|1,pos,val);
push_up(rt);
}
int query(int rt,int l,int r)
{
if(tree[rt].r<l||tree[rt].l>r) return 0;
if(tree[rt].l>=l&&tree[rt].r<=r)
{
return tree[rt].GCD;
}
int ans=0;
if(tree[rt].mid>=l) ans=__gcd(ans,query(rt<<1,l,r));
if(tree[rt].mid<r) ans=__gcd(ans,query(rt<<1|1,l,r));
return ans;
}
int findpos(int rt,int l,int r,int x)
{
if(l==r) return 1;//查询到长度为1的区间,说明操作合法
int ansl=x,ansr=x;
if(tree[rt].mid>=l) ansl=query(rt<<1,l,tree[rt].mid);
if(tree[rt].mid<r) ansr=query(rt<<1|1,tree[rt].mid+1,r);
ansl=__gcd(ansl,x);
ansr=__gcd(ansr,x);
if(ansl!=x&&ansr!=x) return -1;//两个子区间的gcd都不是x的倍数,一定不合法
else if(ansl==x&&ansr==x) return 1;//两个子区间的gcd都是x的倍数,一定合法
else if(ansl==x) return findpos(rt<<1|1,tree[rt].mid+1,r,x);//转换为子问题求解
else return findpos(rt<<1,l,tree[rt].mid,x);
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build(1,1,n);
int q;
scanf("%d",&q);
while(q--)
{
int op,l,r,x;
scanf("%d",&op);
if(op==1)
{
scanf("%d%d%d",&l,&r,&x);
if(findpos(1,l,r,x)==1) puts("YES");
else puts("NO");
}
else
{
scanf("%d%d",&l,&x);
update(1,l,x);
}
}
return 0;
}
线段树第三题
链接:
Codeforces Round #111 (Div. 2) E. Buses and People
题意
有n个公交车,每个公交车的信息有
s
i
s_i
si(公交车起始下标),
f
i
f_i
fi(公交车终点下标),
t
i
t_i
ti(公交车发车时间)。
有m个乘客,每个乘客的信息有
l
i
l_i
li(乘客起始下标),
r
i
r_i
ri(乘客下车的下标),
b
i
b_i
bi(乘客上车时间)。
乘客
i
i
i坐上公交车
j
j
j的前提是
s
j
≤
l
i
s_j \leq l_i
sj≤li,
r
i
≤
f
j
r_i \leq f_j
ri≤fj,
b
i
≤
t
j
b_i \leq t_j
bi≤tj,问每一个乘客能坐的公交车中发车时间最小的公交车编号。
做法
首先先将所有公交车和乘客放到一起,之后按照起始下标排序,这样就能保证每个人乘客之前的公交车一定满足 s j ≤ l i s_j \leq l_i sj≤li。
之后我们对时间进行离散化建立线段树,每个节点存储当前t能到达的最大的 f f f,对于每个乘客,我们只需要查找小于等于 t i t_i ti的时间区间中最小的满足 r i ≤ f j r_i \leq f_j ri≤fj的t即可。
所以问题就转换为给定一个区间,求区间内最小的下标满足权值大于某个给定值。这个问题只需要在线段树上优先查询左子区间是否有满足情况的点,向下递归询问即可,注意要用区间max进行剪枝。
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 2e5+5;
struct T
{
int l,r,mid;
int val,id;
}tree[maxn<<2];
void push_up(int rt)
{
tree[rt].val=max(tree[rt<<1].val,tree[rt<<1|1].val);
}
void build(int rt ,int l,int r)
{
tree[rt].l=l;
tree[rt].r=r;
if(l==r)
{
tree[rt].id=-1;
tree[rt].val=0;
return ;
}
int mid=tree[rt].mid=l+r>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
push_up(rt);
}
void update(int rt,int pos,int val,int id)
{
if(tree[rt].l==tree[rt].r)
{
tree[rt].val=max(tree[rt].val,val);
tree[rt].id=id;
return ;
}
if(pos<=tree[rt].mid) update(rt<<1,pos,val,id);
else update(rt<<1|1,pos,val,id);
push_up(rt);
}
int query(int rt,int val,int l,int r)
{
if(tree[rt].l>=l&&tree[rt].r<=r)
{
if(tree[rt].val<val) return -1;
}
if(tree[rt].l==tree[rt].r) return tree[rt].id;
int ans=-1;
if(tree[rt].mid>=l)
{
ans=query(rt<<1,val,l,r);
if(ans!=-1) return ans;
}
if(tree[rt].id<r)
{
ans=query(rt<<1|1,val,l,r);
if(ans!=-1) return ans;
}
return -1;
}
struct Bus
{
int l,r,t,id;
bool operator<(const Bus y)const
{
if(l==y.l) return id<y.id;//要注意一个l同时有公交车和乘客,先更新公交车信息。
return l<y.l;
}
}bus[maxn<<2];
int Hash[maxn<<2],cnt;
int ans[maxn<<2];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n+m;i++)
{
scanf("%d%d%d",&bus[i].l,&bus[i].r,&bus[i].t);
bus[i].id=i;
Hash[++cnt]=bus[i].t;
}
sort(bus+1,bus+1+n+m);
sort(Hash+1,Hash+1+cnt);
build(1,1,cnt);
int d=unique(Hash+1,Hash+1+cnt)-Hash-1;//对时间进行离散化
for(int i=1;i<=n+m;i++)
{
int pos=lower_bound(Hash+1,Hash+1+d,bus[i].t)-Hash;
if(bus[i].id<=n) update(1,pos,bus[i].r,bus[i].id);//公交车信息进行更新
else ans[bus[i].id-n]=query(1,bus[i].r,pos,cnt);//乘客进行查询
}
for(int i=1;i<=m;i++) printf("%d ",ans[i]);
return 0;
}
线段树第四题
链接:
Codeforces Beta Round #19 D. Points
题意
给你一个笛卡尔坐标系,现在要支持三种操作,第一种操作是添加一个点(x,y),第二种操作是删除一个点(x,y), 第三种操作是查询严格在点(x,y)右上角的点中,横坐标最小的点,如果有多个点,选择纵坐标最小的那个。
做法
首先题中给出的坐标范围都是1e9,所以需要对x轴进行离散化,建立线段树,每个节点存储区间内所有点中y的最大值,所以叶子节点存储的是当前x下y的最大值。 之后对于每个x开一个set存储横坐标为x的所有y。
之后对于操作1,2,我们只需要单点更新,并且在set中进行insert和erase。
对于操作3,我们首先转换为经典问题,求x+1到inf区间内最小的下标满足权值大于y,我们只要在线段树上优先看左子树是否有满足条件的点,转换为自问题递归求解即可,注意要用区间max进行剪枝,不然复杂度会退化。
这样我们就知道最小的存在大于y的下标x,之后在x所在的set中upper_bound查找第一个大于y的值即可。
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
const int maxn = 2e5+5;
struct T
{
int l,r,mid;
int val,id;
}tree[maxn<<2];
multiset<int> s[maxn<<2];
void push_up(int rt)
{
tree[rt].val=max(tree[rt<<1].val,tree[rt<<1|1].val);
}
void build(int rt,int l,int r)
{
tree[rt].l=l;
tree[rt].r=r;
tree[rt].val=0;
if(l==r)
{
s[l].insert(-1);
tree[rt].val=-1;
return ;
}
int mid=tree[rt].mid=l+r>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
push_up(rt);
return ;
}
void update(int rt,int pos,int val,int flag)
{
if(tree[rt].l==tree[rt].r)//递归到叶子节点更新set
{
if(flag==0) s[tree[rt].l].erase(val);
else s[tree[rt].l].insert(val);
tree[rt].id=pos;
tree[rt].val=*(s[tree[rt].l].rbegin());
return ;
}
if(pos<=tree[rt].mid) update(rt<<1,pos,val,flag);
else update(rt<<1|1,pos,val,flag);
push_up(rt);
return ;
}
int query(int rt,int val,int l,int r)
{
if(tree[rt].l>r||tree[rt].r<l) return -1;
if(tree[rt].l>=l&&tree[rt].r<=r)//区间最大值进行剪枝
{
if(tree[rt].val<=val) return -1;
}
if(tree[rt].l==tree[rt].r) return tree[rt].id;
if(tree[rt].mid>=l)//优先看左区间是否有满足情况的点
{
int res=query(rt<<1,val,l,r);
if(res!=-1) return res;
}
if(tree[rt].mid<r)
{
int res=query(rt<<1|1,val,l,r);
if(res!=-1) return res;
}
return -1;
}
struct data
{
int flag,x,y;
}p[maxn];
char op[maxn];
int d;
int a[maxn];//原数组
int b[maxn];//每个位置离散后的值
int c[maxn];//表示离散后为i的原来的值为c[i]
int Hash[maxn];//hash去重数组
void GetHash(int a[],int n)
{
for(int i=1;i<=n;i++) Hash[i]=a[i];
sort(Hash+1,Hash+1+n);
d=unique(Hash+1,Hash+1+n)-Hash-1;
for(int i=1;i<=n;i++)
{
b[i]=lower_bound(Hash+1,Hash+1+d,a[i])-Hash;
c[b[i]]=a[i];
}
}
int main()
{
int n,x,y;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s%d%d", op,&x,&y);
if(op[0]=='a') p[i].flag=1;
else if(op[0]=='r') p[i].flag=2;
else p[i].flag=3;
p[i].x=x;
p[i].y=y;
a[i]=x;
}
GetHash(a,n);//对x轴进行离散化
build(1,1,d);//对x轴建立线段树
for(int i=1;i<=n;i++)
{
if(p[i].flag==1) update(1,b[i],p[i].y,1);
else if(p[i].flag==2) update(1,b[i],p[i].y,0);
else
{
int res=query(1,p[i].y,b[i]+1,d);
if(res==-1) printf("-1\n");
else
{
set<int>::iterator it = s[res].upper_bound(p[i].y);
printf("%d %d\n",c[res],(*it));
}
}
}
return 0;
}
线段树第五题
链接:
CodeForces-483D Interesting Array
题意
让你构造一个数列,满足m
种限制条件,每种限制条件是l,r,x
,要求构造的序列区间[l,r]
与运算的值结果为x
。
做法
首先由一个拆位的做法,对于每次询问,拆成30
位分别进行构造,对于每个为0
的位,区间查询这一位下[l,r]
的和是不是等于区间长度,也就是看区间[l,r]
与的结果是否为0。对于每个为1的位,区间更新。但是这个做法是两个log
的,楼主并有没卡过去。
之后楼主的做法是这样的,首先对于每个查询,我们可以看当前[l,r]
区间与的结果,如果这个结果是x
的子集(这里的子集是二进制下的01序列),那么说明我们可以通过区间或运算让区间[l,r]
的结果变成x
,否则一定不行会破坏之前的关系,于是只需要线段树维护区间与运算,区间更新或运算即可。(要注意同一个区间多次询问的情况特判一下)。
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
typedef pair<int,int> pii;
const int maxn = 1e5+5;
struct T
{
int l,r,mid;
int sum,add;
}tree[maxn<<2];
void push_up(int rt)
{
tree[rt].sum=tree[rt<<1].sum&tree[rt<<1|1].sum;
}
void push_down(int rt)
{
if(tree[rt].add!=0)
{
int tmp=tree[rt].add;
tree[rt<<1].add=tree[rt<<1].add|tmp;
tree[rt<<1].sum=tree[rt<<1].sum|tmp;
tree[rt<<1|1].add=tree[rt<<1|1].add|tmp;
tree[rt<<1|1].sum=tree[rt<<1|1].sum|tmp;
tree[rt].add=0;
}
}
void build(int rt,int l,int r)
{
tree[rt].l=l;
tree[rt].r=r;
tree[rt].sum=0;
tree[rt].add=0;
if(l==r) return ;
int mid=tree[rt].mid=l+r>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
return ;
}
void update(int rt,int l,int r,int val)
{
if(tree[rt].l>r||tree[rt].r<l) return ;
if(tree[rt].sum==tree[rt].r-tree[rt].l+1) return ;
if(tree[rt].l>=l&&tree[rt].r<=r)
{
tree[rt].sum=tree[rt].sum|val;
tree[rt].add=tree[rt].add|val;
return ;
}
push_down(rt);
if(tree[rt].mid>=l) update(rt<<1,l,r,val);
if(tree[rt].mid<r) update(rt<<1|1,l,r,val);
push_up(rt);
return ;
}
int query(int rt,int l,int r)
{
if(tree[rt].l>r||tree[rt].r<l) return 0;
if(tree[rt].l>=l&&tree[rt].r<=r) return tree[rt].sum;
push_down(rt);
int ans=(1<<30)-1;
if(tree[rt].mid>=l) ans=ans&query(rt<<1,l,r);
if(tree[rt].mid<r) ans=ans&query(rt<<1|1,l,r);
push_up(rt);
return ans;
}
int ans[maxn];
map<pii,int> mp;
int main()
{
int n,m,l,r,q;
scanf("%d%d",&n,&m);
build(1,1,n);
int flag=0;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&l,&r,&q);
if(mp.count(pii(l,r)))
{
if(mp[pii(l,r)]!=q) flag=1;
}
mp[pii(l,r)]=q;
if(flag==1) continue;
int res=query(1,l,r);
if((q&res)==res)
{
int tmp=q-res;
update(1,l,r,tmp);
}
else flag=1;
}
if(flag==1) return 0*puts("NO");
for(int i=1;i<=n;i++) ans[i]=query(1,i,i);
puts("YES");
for(int i=1;i<=n;i++) printf("%d ",ans[i]);
return 0;
}
线段树第六题
链接:
Educational Codeforces Round 52 C. Make It Equal
题意:
给你一些从左到右摆放的n
堆正方体,每堆正方体由一些正方体堆叠而成,现在每次可以沿着某个高度砍一刀,这个高度之上的正方体都会被砍掉,要求是掉落的正方体个数不超过k
,问最少砍多少刀能让所有正方体高度相同。
做法:
首先可以桶排序,高度从高到低统计出每种高度正方体高度的个数,之后从高到低贪心的看是不是能砍即可。时间复杂度
O
(
n
log
n
)
O(n \log n)
O(nlogn)
但是楼主拉线段树专题不小心看到了这道题,于是给出线段树的做法。首先依旧是统计出每种高度的正方体的个数,之后我们统计前缀和,之后问题就转换为区间[1,200000]
内查找小于k的最大值,找到之后区间更新即可。时间复杂度
O
(
n
log
n
)
O(n \log n)
O(nlogn),但是这个做法不太优雅,要注意很多细节。
两个做法都要注意不需要砍的情况。
代码:
做法一:
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 4e5+5;
ll a[maxn],b[maxn],sum[maxn];
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
sort(a+1,a+1+n);
if(a[1]==a[n])
{
puts("0");
return 0;
}
int pos=1;
for(int i=1;i<=n;i++)
{
while(pos<=200000&&pos<=a[i])
{
sum[pos]=(n-i+1);
pos++;
}
}
for(int i=1;i<=200000;i++) b[i]=sum[200000-i+1];
pos=1;
ll cnt=0;
int ans=0;
while(pos<=200000)
{
if(cnt+b[pos]<=k)
{
cnt+=b[pos];
pos++;
if(pos==200001) ans++;
}
else
{
ans++;
if(b[pos]==n) break;
cnt=b[pos];
pos++;
}
}
printf("%d\n",ans);
return 0;
}
做法二:
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
const int maxn = 4e5+5;
struct T
{
int l,r,mid;
ll add,val;
}tree[maxn<<2];
ll a[maxn];
ll sum[maxn];
ll b[maxn];
void up(int rt)
{
tree[rt].val=min(tree[rt<<1].val,tree[rt<<1|1].val);
return;
}
void down(int rt)
{
if(tree[rt].add!=0)
{
ll tmp=tree[rt].add;
tree[rt<<1].val+=tmp;
tree[rt<<1|1].val+=tmp;
tree[rt<<1].add+=tmp;
tree[rt<<1|1].add+=tmp;
tree[rt].add=0;
}
}
void build(int rt,int l,int r)
{
tree[rt].l=l;
tree[rt].r=r;
tree[rt].add=0;
if(l==r)
{
tree[rt].val=b[l];
return ;
}
int mid=tree[rt].mid=l+r>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
up(rt);
}
pll query(int rt,int l,int r,ll val)
{
if(tree[rt].val>val) return pll(-1,-1);
if(tree[rt].l==tree[rt].r) return pll(tree[rt].l,tree[rt].val);
down(rt);
if(tree[rt<<1|1].val<=val) return query(rt<<1|1,l,r,val);
else return query(rt<<1,l,r,val);
}
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
sort(a+1,a+1+n);
if(a[1]==a[n])
{
puts("0");
return 0;
}
int pos=1;
for(int i=1;i<=n;i++)
{
while(pos<=200000&&pos<=a[i])
{
sum[pos]=(n-i+1);
pos++;
}
}
for(int i=1;i<=200000;i++) b[i]=sum[200000-i+1];
for(int i=1;i<=200000;i++) b[i]=b[i-1]+b[i];
build(1,1,200000);
pll tt=pll(0,0);
int ans=0;
ll cc=0;
while(true)
{
tt=query(1,1,200000,k);
ans++;
tree[1].val-=tt.second;
tree[1].add-=tt.second;
if(tt.first==200000) break;
cc+=tt.second;
ll tm=b[tt.first+1]-cc;
if(tm==n) break;
}
printf("%d\n",ans);
return 0;
}
线段树第七题
链接:
http://codeforces.com/problemset/problem/296/C
题意:
给你n
个数,有m
次操作,每次操作为区间加,现在有k
次大操作,每次大操作执行m
个操作的[l,r]
区间,问k次大操作之后这n个数变成什么样?
做法:
第一种做法就是先对k次大操作进行差分,就知道每个小操作执行次数,之后线段树维护区间加即可。
第二种做法就是知道每个小操作执行次数之后,在原数组上再差分一次,就可以得到每个数加多少值,
O
(
n
)
O(n)
O(n)即可完成。
代码:
做法一:
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
struct T
{
int l,r,mid;
ll val,add;
}tree[maxn<<2];
int a[maxn];
void up(int rt)
{
tree[rt].val=tree[rt<<1].val+tree[rt<<1|1].val;
}
void down(int rt)
{
if(tree[rt].add)
{
ll tmp=tree[rt].add;
tree[rt<<1].add+=tmp;
tree[rt<<1].val+=1LL*(tree[rt<<1].r-tree[rt<<1].l+1)*tmp;
tree[rt<<1|1].add+=tmp;
tree[rt<<1|1].val+=1LL*(tree[rt<<1|1].r-tree[rt<<1|1].l+1)*tmp;
tree[rt].add=0;
}
}
void build(int rt,int l,int r)
{
tree[rt].l=l;
tree[rt].r=r;
tree[rt].add=0;
if(l==r)
{
tree[rt].val=a[l];
return ;
}
int mid=tree[rt].mid=l+r>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
up(rt);
}
void update(int rt,int l,int r,ll val)
{
if(tree[rt].l>r||tree[rt].r<l) return ;
if(tree[rt].l>=l&&tree[rt].r<=r)
{
tree[rt].add+=val;
tree[rt].val+=1LL*(tree[rt].r-tree[rt].l+1)*val;
return ;
}
down(rt);
if(tree[rt].mid>=l) update(rt<<1,l,r,val);
if(tree[rt].mid<r) update(rt<<1|1,l,r,val);
up(rt);
return ;
}
ll query(int rt,int pos)
{
if(tree[rt].l==tree[rt].r) return tree[rt].val;
down(rt);
if(pos<=tree[rt].mid) return query(rt<<1,pos);
else return query(rt<<1|1,pos);
}
int l[maxn],r[maxn],val[maxn],cnt[maxn];
int tt[maxn];
int main()
{
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build(1,1,n);
for(int i=1;i<=m;i++) scanf("%d%d%d",&l[i],&r[i],&val[i]);
for(int i=1;i<=k;i++)
{
int u,v;
scanf("%d%d",&u,&v);
tt[u]++;
tt[v+1]--;
}
int tmp=0;
for(int i=1;i<=m;i++)
{
tmp+=tt[i];
cnt[i]=tmp;
}
for(int i=1;i<=m;i++) update(1,l[i],r[i],1LL*cnt[i]*val[i]);
for(int i=1;i<=n;i++) printf("%lld ",query(1,i));
return 0;
}
做法二:
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
int a[maxn];
int l[maxn],r[maxn],val[maxn],cnt[maxn];
int tt[maxn];
ll sum[maxn];
int main()
{
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=m;i++) scanf("%d%d%d",&l[i],&r[i],&val[i]);
for(int i=1;i<=k;i++)
{
int u,v;
scanf("%d%d",&u,&v);
tt[u]++;
tt[v+1]--;
}
int tmp=0;
for(int i=1;i<=m;i++)
{
tmp+=tt[i];
cnt[i]=tmp;
}
ll tp=0;
for(int i=1;i<=m;i++)
{
sum[l[i]]+=1LL*cnt[i]*val[i];
sum[r[i]+1]-=1LL*cnt[i]*val[i];
}
for(int i=1;i<=n;i++)
{
tp+=sum[i];
printf("%lld ",a[i]+tp);
}
return 0;
}
线段树第八题:
链接:
http://codeforces.com/contest/444/problem/C
题意:
给你n
个元素,第i
个元素最初的颜色是i
,最初每个元素的权值为0
,有两种操作,第一种操作是区间赋值x
,如果之前元素的颜色为y
,那么赋值之后他的权值增加abs(x-y)
,第二种操作是查询所有元素的权值和。
做法:
首先可以想象如果每次操作区间很大,那么所有元素趋向颜色相同,如果操作区间很小,则可以暴力更新,那么我们可以维护区间颜色是否相同,如果相同则用lazy
进行更新,否则再去子区间进行更新,这里要注意lazy
对于子区间的更新每次都有一个权值的增加,这个也要用一个lazy
数组维护一下。
代码:
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
struct T
{
int l,r,mid;
int col,add;
ll sum,inc;
}tree[maxn<<2];
int col[maxn];
void up(int rt)
{
if(tree[rt<<1].col==tree[rt<<1|1].col) tree[rt].col=tree[rt<<1].col;
else tree[rt].col=0;
tree[rt].sum=tree[rt<<1].sum+tree[rt<<1|1].sum;
}
int maxx=0;
void down(int rt)
{
if(tree[rt].add!=0)
{
int tmp=tree[rt].add;
tree[rt<<1].col=tmp;
tree[rt<<1].add=tmp;
tree[rt<<1|1].col=tmp;
tree[rt<<1|1].add=tmp;
tree[rt].add=0;
}
if(tree[rt].inc!=0)
{
ll tmp=tree[rt].inc;
tree[rt<<1].sum+=1LL*tmp*(tree[rt<<1].r-tree[rt<<1].l+1);
tree[rt<<1].inc+=tmp;
tree[rt<<1|1].sum+=1LL*tmp*(tree[rt<<1|1].r-tree[rt<<1|1].l+1);
tree[rt<<1|1].inc+=tmp;
tree[rt].inc=0;
}
}
void build(int rt,int l,int r)
{
tree[rt].l=l;
tree[rt].r=r;
tree[rt].add=0;
tree[rt].inc=0;
if(l==r)
{
tree[rt].col=col[l];
tree[rt].sum=0;
return ;
}
int mid=tree[rt].mid=l+r>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
up(rt);
return ;
}
void update(int rt,int l,int r,int co)
{
if(tree[rt].l>r||tree[rt].r<l) return ;
if(tree[rt].l>=l&&tree[rt].r<=r&&tree[rt].col!=0)
{
tree[rt].sum=tree[rt].sum+1LL*abs(co-tree[rt].col)*(tree[rt].r-tree[rt].l+1);
tree[rt].inc+=abs(co-tree[rt].col);
tree[rt].col=co;
tree[rt].add=co;
return ;
}
down(rt);
if(tree[rt].mid>=l) update(rt<<1,l,r,co);
if(tree[rt].mid<r) update(rt<<1|1,l,r,co);
up(rt);
}
ll query(int rt,int l,int r)
{
if(tree[rt].l>r||tree[rt].r<l) return 0;
if(tree[rt].l>=l&&tree[rt].r<=r) return tree[rt].sum;
down(rt);
ll ans=0;
if(tree[rt].mid>=l) ans=ans+query(rt<<1,l,r);
if(tree[rt].mid<r) ans=ans+query(rt<<1|1,l,r);
return ans;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) col[i]=i;
build(1,1,n);
int cnt=0;
while(m--)
{
int op,x,y,z;
scanf("%d",&op);
if(op==1)
{
scanf("%d%d%d",&x,&y,&z);
update(1,x,y,z);
}
else
{
scanf("%d%d",&x,&y);
printf("%lld\n",query(1,x,y));
}
}
return 0;
}
线段树第九题:
链接:
https://codeforces.com/contest/707/problem/D
题意
有n个书架,每个书架上有m个位置可以放书,现在要维护4种操作。
1,x,y
:如果第x个书架的第y个位置没有书,在第x个书架的第y个位置放置一本书
2,x,y
:如果第x个书架的第y个位置有书,把第x个书架的第y个位置的书拿走
3,x
: 将第x书架所有有书的位置的书拿走,将x书架所有没有书的位置放上书
4,x
:返回第x次操作后状态
要求每步操作之后返回当前书的个数。
做法
首先每一步操作都是从之前的某步操作转过来的,所以整个操作顺序是一颗有向树,我们只要按顺序dfs,问题就变成单点更新,区间求和,但是要注意的是,回溯撤销操作的时候要看操作的时候是否对状态进行改变,比如本来有书又放一本书,没有对状态进行改变,回溯操作的时候就不能取走这本书。
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 1e6+5;
struct data
{
int op;
int x,y;
}Q[maxn];
int n,m,q;
struct T
{
int l,r,mid;
int sum,add;
}tree[maxn<<2];
vector<int>G[maxn];
void down(int rt)
{
if(tree[rt].add)
{
tree[rt<<1].sum=(tree[rt<<1].r-tree[rt<<1].l+1)-tree[rt<<1].sum;
tree[rt<<1|1].sum=(tree[rt<<1|1].r-tree[rt<<1|1].l+1)-tree[rt<<1|1].sum;
tree[rt<<1].add=1-tree[rt<<1].add;
tree[rt<<1|1].add=1-tree[rt<<1|1].add;
tree[rt].add=0;
}
}
void up(int rt)
{
tree[rt].sum=tree[rt<<1].sum+tree[rt<<1|1].sum;
}
void build(int rt,int l,int r)
{
tree[rt].l=l;
tree[rt].r=r;
tree[rt].sum=0;
tree[rt].add=0;
if(l==r) return;
int mid=tree[rt].mid=l+r>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
}
bool update(int rt,int pos,int val)
{
if(tree[rt].l==tree[rt].r)
{
int tt=tree[rt].sum;
tree[rt].sum=val;
return (tt!=val);
}
down(rt);
bool ans;
if(tree[rt].mid>=pos) ans=update(rt<<1,pos,val);
else ans=update(rt<<1|1,pos,val);
up(rt);
return ans;
}
void update_(int rt,int l,int r)
{
if(tree[rt].l>r||tree[rt].r<l) return ;
if(tree[rt].l>=l&&tree[rt].r<=r)
{
tree[rt].add=1-tree[rt].add;
tree[rt].sum=tree[rt].r-tree[rt].l+1-tree[rt].sum;
return ;
}
down(rt);
if(tree[rt].mid>=l) update_(rt<<1,l,r);
if(tree[rt].mid<r) update_(rt<<1|1,l,r);
up(rt);
}
int ans[maxn];
void dfs(int rt)
{
int flag=0;
if(rt!=0)
{
if(Q[rt].op==1)
{
if(update(1,(Q[rt].x-1)*m+Q[rt].y,1)) flag=1;
}
else if(Q[rt].op==2)
{
if(update(1,(Q[rt].x-1)*m+Q[rt].y,0)) flag=1;
}
else if(Q[rt].op==3) update_(1,(Q[rt].x-1)*m+1,Q[rt].x*m);
}
ans[rt]=tree[1].sum;
for(int i=0;i<G[rt].size();i++)
{
dfs(G[rt][i]);
}
if(rt!=0)
{
if(Q[rt].op==1&&flag==1) update(1,(Q[rt].x-1)*m+Q[rt].y,0);
else if(Q[rt].op==2&&flag==1) update(1,(Q[rt].x-1)*m+Q[rt].y,1);
else if(Q[rt].op==3) update_(1,(Q[rt].x-1)*m+1,Q[rt].x*m);
}
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=q;i++)
{
scanf("%d",&Q[i].op);
if(Q[i].op==1||Q[i].op==2)
{
G[i-1].push_back(i);
scanf("%d%d",&Q[i].x,&Q[i].y);
}
else if(Q[i].op==3)
{
G[i-1].push_back(i);
scanf("%d",&Q[i].x);
}
else
{
scanf("%d",&Q[i].x);
G[Q[i].x].push_back(i);
}
}
build(1,1,n*m);
dfs(0);
for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
return 0;
}