线段树
线段树是用一种树状结构来存储一个连续区间的信息的数据结构。
线段树的几点性质:
1.线段树是平衡(左子树和右子树的高度差最大为1)的二叉树,最大深度为log2n(n为线段树所表示区间的长度)
2.树中的每一个节点代表对应一个区间(叶子节点是一个点…)
3.每个节点(所代表的区间)完全包含它的所有子孙节点
4.对于任意两个节点(所代表的区间):要么完全包含,要么互不相交
5.在进行区间操作和统计时把区间等价转换成若干个子区间(logn个)的相同操作。
关键在于理解好:
这棵树的每一个结点代表的是一个区间,存储有关该区间的信息,不同点/区间用不同编号id来表示~
—重新学习线段树—
//数据结构:
const int N=1e5;
int n,a[N],tree[N<<2]; //看到大佬一般都开 N<<2 不太懂为啥 就行了~~~ //知识:深度为k的二叉树结点为 2^k-1 第k层结点为 2^(k-1)-1
//树上的每个点是一个线段/区间
//tree[]:每个元素代表树上的一个点,存放与该结点有关的信息
模板:
洛谷 P3372 【模板】线段树 1
区间加
返回区间和
参考代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define gmid (l+r>>1)
#define lson (rt<<1)
#define rson (rt<<1|1)
const int N=1e5+5;
int n,m,a[N];
//线段树的数据结构
int sum[N<<2],ly[N<<2];
//建树
void build(int rt,int l,int r){
if(l==r) {
sum[rt]=a[l];
return;
}
//建左右子树
int mid=gmid;
build(lson,l,mid);
build(rson,mid+1,r);
//合并左右树的信息 //——》这也是线段树的一个缺陷,只能解决区间的信息能有左右区间合并来的问题
sum[rt]=(sum[lson]+sum[rson]);
}
//传递 //更改点 lson 和 rson 的信息
void pd(int rt,int l,int r)
{
int mid=gmid;
sum[lson]+=(mid-l+1)*ly[rt];
sum[rson]+=(r-mid)*ly[rt];
ly[lson]+=ly[rt];
ly[rson]+=ly[rt];
ly[rt]=0;
}
//区间加
void add(int rt,int l,int r,int x,int y,int z){
if(l>y || r<x) return;
if(l>=x && r<=y){
sum[rt]+=(r-l+1)*z;
ly[rt]+=z;
return;
}
//传递
if(ly[rt]) pd(rt,l,r);
//左右子树
int mid=gmid;
add(lson,l,mid,x,y,z);
add(rson,mid+1,r,x,y,z);
//合并
sum[rt]=sum[lson]+sum[rson];
}
//返回区间和
int qry(int rt,int l,int r,int x,int y) //查询区间[x,y]的信息——》sum[rt]里存着[l,r]的信息,即区间[l,r]的和
{
if(l>y || r<x) return 0;
if(l>=x && r<=y)
return sum[rt];
if(ly[rt]) pd(rt,l,r);
int mid=gmid;
return qry(lson,l,mid,x,y)+qry(rson,mid+1,r,x,y);
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i)
cin>>a[i];
build(1,1,n);
int t,t1,t2,t3;
while(m--){
cin>>t;
if(t==1){
cin>>t1>>t2>>t3;
add(1,1,n,t1,t2,t3);
}
else{
cin>>t1>>t2;
cout<<qry(1,1,n,t1,t2)<<'\n';
}
}
}
洛谷 P3373 【模板】线段树 2
区间加,乘
返回区间和
参考代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define gmid (l+r>>1)
#define lson (rt<<1)
#define rson (rt<<1|1)
const int N=1e5+5;
int n,m,mod,a[N];
//线段树数据结构
int sum[N<<2],ly1[N<<2],ly2[N<<2];
void build(int rt,int l,int r){
if(l>r) return;
//初始化 懒惰标记
ly1[rt]=0;
ly2[rt]=1;
if(l==r){
sum[rt]=a[l]%mod;
return;
}
//左右子树
int mid=gmid;
build(lson,l,mid);
build(rson,mid+1,r);
//合并 //初始化区间和
sum[rt]=(sum[lson]+sum[rson])%mod;
}
//向下传递--》更改点 id*2 和 id*2+1
void pd(int l,int r,int rt){
int mid=gmid;
sum[lson]=(sum[lson]*ly2[rt]+(mid-l+1)*ly1[rt])%mod;
sum[rson]=(sum[rson]*ly2[rt]+(r-mid)*ly1[rt])%mod;
ly1[lson]=(ly1[lson]*ly2[rt]+ly1[rt])%mod;
ly1[rson]=(ly1[rson]*ly2[rt]+ly1[rt])%mod;
ly2[lson]=(ly2[lson]*ly2[rt])%mod;
ly2[rson]=(ly2[rson]*ly2[rt])%mod;
ly1[rt]=0;
ly2[rt]=1;
}
//区间乘
void mul(int rt,int l,int r,int x,int y,int z){
if(l>y||r<x) return;
if(l>=x&&r<=y) {
sum[rt]=(sum[rt]*z)%mod;
ly1[rt]=ly1[rt]*z%mod;
ly2[rt]=ly2[rt]*z%mod;
return;
}
//向下传递
pd(l,r,rt);
//左右子树
int mid=gmid;
mul(lson,l,mid,x,y,z);
mul(rson,mid+1,r,x,y,z);
//合并
sum[rt]=(sum[lson]+sum[rson])%mod;
}
//区间加
void add(int rt,int l,int r,int x,int y,int z){
if(l>y || r<x) return;
if(l>=x && r<=y){
sum[rt]=(sum[rt]+(r-l+1)*z)%mod;
ly1[rt]=(ly1[rt]+z)%mod;
return;
}
//向下传递
pd(l,r,rt);
//左右子树
int mid=gmid;
add(lson,l,mid,x,y,z);
add(rson,mid+1,r,x,y,z);
//合并
sum[rt]=(sum[lson]+sum[rson])%mod;
}
int qry(int rt,int l,int r,int x,int y){
if(l>y || r<x) return 0;
if(l>=x&&r<=y) return sum[rt];
pd(l,r,rt);
int mid=gmid;
return (qry(lson,l,mid,x,y)+qry(rson,mid+1,r,x,y))%mod;
}
signed main() {
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>m>>mod;
for(int i=1;i<=n;++i) //将序列数读入
cin>>a[i];
build(1,1,n); //建线段树
int t,t1,t2,t3;
while(m--) {
cin>>t;
if(t==1) {
cin>>t1>>t2>>t3;
mul(1,1,n,t1,t2,t3); //区间乘
}
else if(t==2) {
cin>>t1>>t2>>t3;
add(1,1,n,t1,t2,t3); //区间加
}
else {
cin>>t1>>t2;
cout<<qry(1,1,n,t1,t2)<<'\n'; //访问区间数的和
}
}
}
简单题
敌兵布阵
//单点修改,区间查询总和
#include <bits/stdc++.h>
using namespace std;
#define ms(a,x) memset(a,x,sizeof a)
#define ll long long
const int N=5e4+1;
ll T,cs,n,a[N];
string s;
ll sum[N<<2],ly[N<<2];
void build(ll id,ll l,ll r)
{
if(l>r) return;
if(l==r)
{
sum[id]=a[l];
return;
}
ll mid=(l+r)>>1;
build(id*2,l,mid);
build(id*2+1,mid+1,r);
sum[id]=sum[id*2]+sum[id*2+1];
}
void pb(ll id,ll l,ll r)
{
ll mid=(l+r)>>1;
sum[id*2]+=(mid-l+1)*ly[id];
sum[id*2+1]+=(r-mid)*ly[id];
ly[id*2]+=ly[id];
ly[id*2+1]+=ly[id];
ly[id]=0;
}
void add(ll id,ll l,ll r,ll x,ll y,ll z)
{
if(l>y || r<x) return;
if(l>=x && r<=y)
{
sum[id]+=(r-l+1)*z;
ly[id]+=z;
return;
}
pb(id,l,r);
ll mid=(l+r)>>1;
add(id*2,l,mid,x,y,z);
add(id*2+1,mid+1,r,x,y,z);
sum[id]=sum[id*2]+sum[id*2+1];
}
ll qry(ll id,ll l,ll r,ll x,ll y)
{
if(l>y || r<x) return 0;
if(l>=x && r<=y) return sum[id];
pb(id,l,r);
ll mid=(l+r)>>1;
return qry(id*2,l,mid,x,y)+qry(id*2+1,mid+1,r,x,y);
}
int main()
{
scanf("%lld",&T);
while(T--)
{
cs++;
printf("Case %lld:\n",cs);
ms(a,0);
ms(sum,0);
ms(ly,0);
scanf("%lld",&n);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
build(1,1,n);
while(cin>>s)
{
if(s=="End") break;
if(s=="Add")
{
ll p,v;
scanf("%lld %lld",&p,&v);
add(1,1,n,p,p,v);
}
else if(s=="Sub")
{
ll p,v;
scanf("%lld %lld",&p,&v);
add(1,1,n,p,p,0-v);
}
else
{
ll l,r;
scanf("%lld %lld",&l,&r);
printf("%lld\n",qry(1,1,n,l,r));
}
}
}
}
//单点修改,区间查询最大值
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ms(a,x) memset(a,x,sizeof a)
const int N=2e5+1;
ll n,m,a[N];
ll mx[N<<2],ly[N<<2];
void build(ll id,ll l,ll r)
{
if(l>r) return;
if(l==r)
{
mx[id]=a[l];
return;
}
ll mid=(l+r)>>1;
build(id*2,l,mid);
build(id*2+1,mid+1,r);
mx[id]=max(mx[id*2],mx[id*2+1]);
}
void update(ll id,ll l,ll r,ll x,ll z)
{
if(l>x || r<x) return;
if(l==r && l==x)
{
mx[id]=max(mx[id],z);
return;
}
ll mid=(l+r)>>1;
update(id*2,l,mid,x,z);
update(id*2+1,mid+1,r,x,z);
mx[id]=max(mx[id*2],mx[id*2+1]);
}
ll qry(ll id,ll l,ll r,ll x,ll y)
{
if(l>y || r<x) return LLONG_MIN;
if(l>=x && r<=y) return mx[id];
ll mid=(l+r)>>1;
return max(qry(id*2,l,mid,x,y),qry(id*2+1,mid+1,r,x,y));
}
int main()
{
while(cin>>n>>m)
{
ms(a,0);
ms(mx,0);
ms(ly,0);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
build(1,1,n);
char c;
ll x,y;
while(m--)
{
cin>>c>>x>>y;
if(c=='Q')
printf("%lld\n",qry(1,1,n,x,y));
else
update(1,1,n,x,y);
}
}
}
//区间修改,区间查询
区间修改每个数开平方的和,优化方法:当那个区间的数变为1时,就不用再向下深入去修改一个数的值了。
#include <bits/stdc++.h>
using namespace std;
#define ms(a,x) memset(a,x,sizeof a)
#define ll long long
const int N=1e5+5;
ll n,m,a[N],sum[N<<2],cs;
void build(ll id,ll l,ll r)
{
if(l>r) return;
if(l==r)
{
sum[id]=a[l];
return;
}
ll mid=(l+r)>>1;
build(id*2,l,mid);
build(id*2+1,mid+1,r);
sum[id]=sum[id*2]+sum[id*2+1];
}
void update(ll id,ll l,ll r,ll x,ll y)
{
if(l>y || r<x) return;
if(sum[id]==r-l+1) return;
if(l==r)
{
sum[id]=sqrt(sum[id]);
return;
}
ll mid=(l+r)>>1;
update(id*2,l,mid,x,y);
update(id*2+1,mid+1,r,x,y);
sum[id]=sum[id*2]+sum[id*2+1];
}
ll qry(ll id,ll l,ll r,ll x,ll y)
{
if(l>y || r<x) return 0;
if(l>=x&&r<=y) return sum[id];
ll mid=(l+r)>>1;
return qry(id*2,l,mid,x,y)+qry(id*2+1,mid+1,r,x,y);
}
int main()
{
while(~scanf("%lld",&n))
{
cs++;
printf("Case #%lld:\n",cs);
ms(a,0);
ms(sum,0);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
build(1,1,n);
scanf("%lld",&m);
ll opt,x,y;
while(m--)
{
scanf("%lld %lld %lld",&opt,&x,&y);
if(x>y) swap(x,y); //哇靠!什么傻逼卡点
if(opt==0)
update(1,1,n,x,y);
else
printf("%lld\n",qry(1,1,n,x,y));
}
printf("\n");
}
}
覆盖类型——相当于整个区间每个数修改成同一个值
Count the Colors
区间染色
n行
x1 x2 c 表示[x1,x2]染成c号色,最后统计色段
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ms(a,x) memset(a,x,sizeof a)
const int N=8e3+10;
ll n,a[N],ly[N<<2]; //color[i]:线段树上的第i号点(代表一个区间)的颜色
//线段树的精髓:以点代区间——》达到当我们对一个区间操作时复杂度为O(N)降低为O(1)
//线段树的结点 也可以开一个 结构体。
void build(ll id,ll l,ll r)
{
ly[id]=-1;
if(l>=r) return;
ll mid=(l+r)>>1;
build(id*2,l,mid);
build(id*2+1,mid+1,r);
}
void pb(ll id,ll l,ll r)
{
ly[id*2]=ly[id];
ly[id*2+1]=ly[id];
ly[id]=-1;
}
void add(ll id,ll l,ll r,ll x,ll y,ll z)
{
if(l>y || r<x) return;
if(l>=x&&r<=y)
{
ly[id]=z;
return;
}
if(ly[id]!=-1) pb(id,l,r);
ll mid=(l+r)>>1;
add(id*2,l,mid,x,y,z);
add(id*2+1,mid+1,r,x,y,z);
}
void qry(ll id,ll l,ll r)
{
if(l>r) return;
if(l==r)
{
a[l]=ly[id];
return;
}
if(ly[id]!=-1)
{
for(ll i=l;i<=r;++i)
a[i]=ly[id];
}
else
{
ll mid=(l+r)>>1;
qry(id*2,l,mid);
qry(id*2+1,mid+1,r);
}
}
int main()
{
while(cin>>n)
{
ms(a,-1);
ms(ly,0);
map<int,int> mp;
build(1,0,8000);
ll x,y,z;
for(int i=1;i<=n;++i)
{
scanf("%lld %lld %lld",&x,&y,&z);
add(1,0,8000,x,y-1,z);
}
qry(1,0,8000);
for(int i=0;i<=8000;++i)
if(a[i]!=a[i+1]) mp[a[i]]++;
for(auto x:mp)
{
if(x.first==-1) continue;
else cout<<(x.first)<<' '<<(x.second)<<endl;
}
cout<<endl;
}
}
//注意:比如说染[x,y],其实应该算[x,y-1]
[Just a Hook](https://vjudge.net/problem/HDU-1698
//区间修改——需要懒惰标记,查询总和。
#include <bits/stdc++.h>
using namespace std;
#define ms(a,x) memset(a,x,sizeof a)
#define ll long long
const int N=1e5+1;
ll T,n,q,a[N],sum[N<<2],ly[N<<2];
int cs;
void build(ll id,ll l,ll r)
{
if(l>r) return;
if(l==r)
{
sum[id]=a[l];
return;
}
ll mid=(l+r)>>1;
build(id*2,l,mid);
build(id*2+1,mid+1,r);
sum[id]=sum[id*2]+sum[id*2+1];
}
void pb(ll id,ll l,ll r)
{
ll mid=(l+r)>>1;
sum[id*2]=(mid-l+1)*ly[id];
sum[id*2+1]=(r-mid)*ly[id];
ly[id*2]=ly[id];
ly[id*2+1]=ly[id];
ly[id]=0;
}
void add(ll id,ll l,ll r,ll x,ll y,ll z)
{
if(l>y || r<x) return;
if(l>=x && r<=y)
{
sum[id]=(r-l+1)*z;
ly[id]=z;
return;
}
if(ly[id]) pb(id,l,r);
ll mid=(l+r)>>1;
add(id*2,l,mid,x,y,z);
add(id*2+1,mid+1,r,x,y,z);
sum[id]=sum[id*2]+sum[id*2+1];
}
int main()
{
scanf("%lld",&T);
while(T--)
{
scanf("%lld",&n);
cs++;
ms(a,0);
ms(sum,0);
ms(ly,0);
for(int i=1;i<=n;++i) a[i]=1;
build(1,1,n);
scanf("%lld",&q);
ll x,y,z;
while(q--)
{
scanf("%lld %lld %lld",&x,&y,&z);
add(1,1,n,x,y,z);
}
printf("Case %d: The total value of the hook is %lld.\n",cs,sum[1]);
}
}
扫描线
离散化学习
离散化:
1.unique 去重
2.二分找离散值
/*
unique:"删除"序列中所有相邻的重复元素,只保留一个。
注意点:
1.不能改变数组的大小, unique之后,原序列长度不变,
2.此处删除不是真的删除,是保留原来相同且连续序列的第一个元素,其他元素用后面不重复的元素替换。
3.删除的是相邻的重复元素,所以在使用unique函数之前,一般都会将目标序列进行排序。这样才能去除所有重复元素保留其中一个
4.返回值:没有相邻重复元素的序列的最后一个元素的下一个位置。
5.头文件:#include
6.示例:
vector<>:
#include <bits/stdc++.h>
using namespace std;
int n,t;
vector<int> v;
int main(){
cin>>n;
for(int i=1;i<=n;++i) {
cin>>t;
v.push_back(t);
}
sort(v.begin(),v.end());
int d=unique(v.begin(),v.end())-v.begin();
cout<<d<<'\n';
for(int i=0;i<d;++i)
cout<<v[i]<<' ';
cout<<'\n';
}
数组:
#include <bits/stdc++.h>
using namespace std;
int n,a[100],b[100];
int main(){
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
b[i]=a[i];
}
sort(b+1,b+1+n);
int d=unique(b+1,b+1+n)-b-1;
cout<<d<<'\n';
for(int i=1;i<=n;++i) cout<<b[i]<<' ';
cout<<'\n';
}
*/
//求矩形面积
#include <bits/stdc++.h>
using namespace std;
#define ms(a,x) memset(a,0,sizeof a)
const int N=1e2+5;
struct Line{
double l,r,h;
int d;
bool friend operator<(Line a,Line b)
{
return a.h<b.h;
}
};
Line line[N<<1];
int n,cs,cnt;
double xd[N<<1];
double ans;
struct Node{
double len;
int state;
};
Node node[N<<3];
//线段树上的每个点node[i]标识一些连续区间块的信息
void pushup(int id,int l,int r)
{
if(node[id].state) node[id].len=xd[r+1]-xd[l]; //区间l到区间r的这块由若干个连续区间组成的区间块的总长度=x[r+1]-x[l]
else if(l==r) node[id].len=0; //叶子结点,只表示一个区间,不再会有左右孩子
else node[id].len=node[id<<1].len+node[id<<1|1].len;
}
void update(int id,int l,int r,int x,int y,int z)
{
if(l>y || r<x) return;
if(l>=x && r<=y)
{
node[id].state+=z;
pushup(id,l,r);
return;
}
int mid=(l+r)>>1;
update(id<<1,l,mid,x,y,z);
update(id<<1|1,mid+1,r,x,y,z);
pushup(id,l,r);
}
int main()
{
while(~scanf("%d",&n))
{
if(!n) break;
cs++;
printf("Test case #%d\n",cs);
ms(line,0);
ms(node,0);
cnt=0;
ans=0;
ms(xd,0);
double x1,y1,x2,y2;
for(int i=1;i<=n;++i)
{
scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2); //原来是double要用%lf输入,一开始用%f输入一直没有数字
line[++cnt].l=x1;
line[cnt].r=x2;
line[cnt].h=y1;
line[cnt].d=1;
xd[cnt]=x1;
line[++cnt].l=x1;
line[cnt].r=x2;
line[cnt].h=y2;
line[cnt].d=-1;
xd[cnt]=x2;
}
sort(xd+1,xd+cnt+1);
sort(line+1,line+1+cnt);
int num=unique(xd+1,xd+1+cnt)-xd-1; //不重复元素个数为num个,a[1]~a[num],num个数构成num-1个区间
for(int i=1;i<cnt;++i) //扫描线
{
int a=lower_bound(xd+1,xd+1+num,line[i].l)-xd; //离散值
int b=lower_bound(xd+1,xd+1+num,line[i].r)-xd-1; //离散值
//举个例子,不难看出:line[i].l-line[i].r 覆盖的区间是 line[i].l离散值到line[i].r离散值-1
update(1,1,num-1,a,b,line[i].d);
ans+=node[1].len*(line[i+1].h-line[i].h);
}
printf("Total explored area: %.2lf\n\n",ans);
}
}
区间并
命令:
D X:断掉x号桥
R:修复最近毁掉的桥
Q x:查询包含x的连续区间的最大值
单点修改(断掉),查找含有pos的连续区间最大值。
#include <bits/stdc++.h>
using namespace std;
#define ms(a,x) memset(a,x,sizeof a)
const int N=5e4+5;
int n,m;
//线段树
struct Node{
int l,r,ls,rs,ms;
}node[N<<2];
void build(int id,int l,int r)
{
node[id].l=l;
node[id].r=r;
node[id].ls=node[id].rs=node[id].ms=(r-l+1);
if(l==r) return;
int mid=(l+r)>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
}
int st[N],cnt;
void update(int id,int pos,int flag)
{
//递归终止条件
// cout << node[id].l << ' ' << node[id].r << ' ' << pos << endl;
if(node[id].l==node[id].r && node[id].l==pos) //搜到对应的点
{
node[id].ls=node[id].rs=node[id].ms=flag;
return;
}
int mid=(node[id].l+node[id].r)>>1;
if(pos<=mid)
update(id<<1,pos,flag);
else
update(id<<1|1,pos,flag);
//更新node点
if(node[id<<1].ls==node[id<<1].r-node[id<<1].l+1)
node[id].ls=node[id<<1].ls+node[id<<1|1].ls;
else
node[id].ls=node[id<<1].ls;
if(node[id<<1|1].rs==node[id<<1|1].r-node[id<<1|1].l+1)
node[id].rs=node[id<<1|1].rs+node[id<<1].rs;
else
node[id].rs=node[id<<1|1].rs;
node[id].ms=max({node[id<<1].rs+node[id<<1|1].ls,node[id<<1].ms,node[id<<1|1].ms});
}
int search(int id,int pos) //查找中间连续的值
{
// cout<<" " << node[id].l << ' ' << node[id].r << ' ' << pos<<endl;
if(node[id].l==node[id].r || node[id].ms==0 || node[id].ms==node[id].r-node[id].l+1)
return node[id].ms;
int mid=(node[id].l+node[id].r)>>1;
if(pos<=mid)
{
if(pos>=node[id<<1].r-node[id<<1].rs+1)
return node[id<<1].rs+node[id<<1|1].ls;
else
return search(id<<1,pos);
}
else
{
if(pos<=node[id<<1|1].l+node[id<<1|1].ls-1)
return node[id<<1|1].ls+node[id<<1].rs;
else
return search(id<<1|1,pos);
}
}
int main()
{
while(~scanf("%d %d",&n,&m))
{
ms(node,0);
ms(st,0);
cnt=0;
build(1,1,n);
char c;
int t;
while(m--)
{
cin>>c; //不知道为啥,用scanf()输入字符,后面会输入不进
if(c=='D')
{
scanf("%d",&t);
st[++cnt]=t;
update(1,t,0); //从根结点1开始,破坏点,标记破坏
}
else if(c=='Q')
{
scanf("%d",&t);
printf("%d\n",search(1,t));
}
else
{
t=st[cnt--];
// cout << ":" << t << endl;
update(1,t,1); //从根结点1开始,重建点,标记重建
}
}
}
}
区间修改,查询最左的连续区间的值。
#include <bits/stdc++.h>
using namespace std;
#define ms(a,x) memset(a,x,sizeof a)
const int N=5e4+5;
int n,m;
struct Node{
int l,r,ls,rs,ms,mk; //区间修改,单点查询,要用到线段树的延迟操作。
}node[N<<2];
void build(int id,int l,int r)
{
// cout<<id<<' '<<l<<' '<<r<<endl;
node[id].l=l;
node[id].r=r;
node[id].ls=node[id].rs=node[id].ms=(r-l+1);
node[id].mk=0;
if(l==r) return;
int mid=(l+r)>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
}
//mk==0表示区间不需要下压,mk==1表示区间退房了,mk==2表示区间入房了
void pushdown(int id)
{
if(node[id].mk==1)
{
node[id<<1].mk=node[id<<1|1].mk=1;
node[id<<1].ls=node[id<<1].rs=node[id<<1].ms=0;
node[id<<1|1].ls=node[id<<1|1].rs=node[id<<1|1].ms=0;
}
else
{
int mid=(node[id].l+node[id].r)>>1;
node[id<<1].mk=node[id<<1|1].mk=2;
node[id<<1].ls=node[id<<1].rs=node[id<<1].ms=mid-node[id<<1].l+1;
node[id<<1|1].ls=node[id<<1|1].rs=node[id<<1|1].ms=node[id<<1|1].r-mid;
}
node[id].mk=0;
}
void update(int id,int x,int y,int flag)
{
if(x>node[id].r || y<node[id].l) return;
if(node[id].l>=x && node[id].r<=y) //整段区间作废
{
if(flag==1) //退房
node[id].ls=node[id].ms=node[id].rs=0;
else
node[id].ls=node[id].ms=node[id].rs=node[id].r-node[id].l+1;
node[id].mk=flag;
return;
}
if(node[id].mk)
pushdown(id);
int mid=(node[id].l+node[id].r)>>1;
if(x<=mid)
update(id<<1,x,y,flag);
if(y>mid)
update(id<<1|1,x,y,flag);
//pushup:pushup操作,是操作进入回溯阶段,父亲结点根据左右孩子结点去重新维护自己的值的过程
if(node[id<<1].ls==node[id<<1].r-node[id<<1].l+1)
node[id].ls=node[id<<1].ls+node[id<<1|1].ls;
else
node[id].ls=node[id<<1].ls;
if(node[id<<1|1].rs==node[id<<1|1].r-node[id<<1|1].l+1)
node[id].rs=node[id<<1|1].rs+node[id<<1].rs;
else
node[id].rs=node[id<<1|1].rs;
node[id].ms=max({node[id<<1].ms,node[id<<1|1].ms,node[id<<1].rs+node[id<<1|1].ls});
}
//找可以用的区间的左端点
int qry(int id,int len)
{
if(node[id].l==node[id].r) return node[id].l;
if(node[id].mk) pushdown(id);
int mid=node[id].l+node[id].r>>1;
if(node[id<<1].ms>=len) return qry(id<<1,len); //找左子区间
else if(node[id<<1].rs+node[id<<1|1].ls>=len) return node[id<<1].r-node[id<<1].rs+1; //找中间区间
return qry(id<<1|1,len); //找右子区间
}
int main()
{
while(~scanf("%d %d",&n,&m))
{
int t,x,y;
ms(node,0);
build(1,1,n);
while(m--)
{
scanf("%d",&t);
if(t==1)
{
scanf("%d",&x);
if(node[1].ms>=x)
{
int pos=qry(1,x);
printf("%d\n",pos);
update(1,pos,pos+x-1,1);
}
else
printf("0\n");
}
else
{
scanf("%d %d",&x,&y);
update(1,x,x+y-1,2);
}
}
}
}
权值线段树
线段树求第k大数
题意:有三种操作,1、0 x 向容器中插入一个数x。 2、1 x 在容器中删除一个数x。 3、2 x k,求出容器中大于x的第k个元素。
思路:求出容器中大于x的第k个元素,可以先求出<=x的元素的数量cnt,然后就等价于求区间[1…N]中第Kth = cnt+k的元素…注意:相同的数可以插入多个
#include <bits/stdc++.h>
using namespace std;
#define ms(a,x) memset(a,x,sizeof a)
const int N=1e5+1;
int m,a[N],sum[N<<2];
void update(int id,int l,int r,int pos,int val) //单点修改
{
if(l==r && l==pos)
{
sum[id]+=val;
return;
}
int mid=l+r>>1;
if(pos<=mid) update(id<<1,l,mid,pos,val);
else update(id<<1|1,mid+1,r,pos,val);
sum[id]=sum[id<<1]+sum[id<<1|1];
}
int qry(int id,int l,int r,int pos)
{
if(l>pos) return 0;
if(r<=pos) return sum[id];
int mid=l+r>>1;
return qry(id<<1,l,mid,pos)+qry(id<<1|1,mid+1,r,pos);
}
int qry1(int id,int l,int r,int pos)
{
if(l==r)
return l;
int mid=l+r>>1;
if(sum[id<<1]<pos) return qry1(id<<1|1,mid+1,r,pos-sum[id<<1]);
else return qry1(id<<1,l,mid,pos);
}
int main()
{
while(~scanf("%d",&m))
{
ms(a,0);
ms(sum,0);
int p,e,k;
while(m--)
{
scanf("%d",&p);
if(p==0)
{
scanf("%d",&e);
update(1,1,N,e,1); //单点修改
a[e]++;
}
else if(p==1)
{
scanf("%d",&e);
if(a[e]==0) printf("No Elment!\n");
else
{
a[e]--;
update(1,1,N,e,-1);
}
}
else
{
scanf("%d %d",&e,&k);
//找小于等于1的数有几个
int cnt=qry(1,1,N,e);
if(cnt+k>sum[1]) printf("Not Find!\n");
else printf("%d\n",qry1(1,1,N,cnt+k));
}
}
}
}
可持久化线段树
//单点修改,单点查询
理论学习:有了可持久化数组,便可以实现很多衍生的可持久化功能(例如:可持久化并查集)。
如题,你需要维护这样的一个长度为 N 的数组,支持如下几种操作
- 在某个历史版本上修改某一个位置上的值
- 访问某个历史版本上的某一位置的值
此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)
#include <bits/stdc++.h>
using namespace std;
const int N=1e7+6; //主席树开空间需要注意一下
int n,m,a[N];
struct Node{
int l,r,val;
}tree[N<<2];
int idx;
//新建节点
int clone(int id)
{
idx++;
tree[idx]=tree[id];
return idx;
}
int build(int id,int begin,int end) //结点,区间左边,区间右边
{
id=++idx;
if(begin==end){
tree[id].val=a[begin];
return id;
}
int mid=begin+end>>1;
tree[id].l=build(tree[id].l,begin,mid);
tree[id].r=build(tree[id].r,mid+1,end);
return id;
}
int update(int id,int begin,int end,int x,int val) //树根,区间开始,区间结束,修改位置,修改值
{
id=clone(id);
if(begin==end)
tree[id].val=val;
else
{
int mid=begin+end>>1;
if(x<=mid)
tree[id].l=update(tree[id].l,begin,mid,x,val);
else
tree[id].r=update(tree[id].r,mid+1,end,x,val);
}
return id;
}
int qry(int id,int begin,int end,int x) //树根,区间开始,区间结束,访问位置
{
if(begin==end)
return tree[id].val;
else
{
int mid=begin+end>>1;
if(x<=mid)
return qry(tree[id].l,begin,mid,x);
else
return qry(tree[id].r,mid+1,end,x);
}
}
const int M=1e6+10;
int root[M]; //存放版本
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
root[0]=build(1,1,n);
int rt,opt,x,y;
for(int i=1;i<=m;++i)
{
scanf("%d %d %d",&rt,&opt,&x);
if(opt==1)
{
scanf("%d",&y);
root[i]=update(root[rt],1,n,x,y);
}
else
{
printf("%d\n",qry(root[rt],1,n,x));
root[i]=root[rt];
}
}
}
可持久化权值线段树
可持久化线段树 2
可持久化权值线段树入门题——静态区间第k小。
如题,给定 n 个整数构成的序列 a,将对于指定的闭区间[l,r]查询其区间内的第 k 小值。
#include <bits/stdc++.h>
using namespace std;
const int N=1e7+10;
int n,m,a[N],b[N];
struct Node{
int l,r,val;
}tree[N<<2];
int idx=0;
int build(int id,int begin,int end)
{
id=++idx;
if(begin==end)
{
tree[id].val=0;
return id;
}
int mid=begin+end>>1;
tree[id].l=build(tree[id].l,begin,mid);
tree[id].r=build(tree[id].r,mid+1,end);
return id;
}
int clone(int id)
{
idx++;
tree[idx]=tree[id];
return idx;
}
int update(int id,int begin,int end,int pos,int val)
{
id=clone(id);
if(begin==end)
{
tree[id].val+=val;
return id;
}
int mid=begin+end>>1;
if(pos<=mid) tree[id].l=update(tree[id].l,begin,mid,pos,val);
else tree[id].r=update(tree[id].r,mid+1,end,pos,val);
tree[id].val=tree[tree[id].l].val+tree[tree[id].r].val;
return id;
}
int qry(int u,int v,int begin,int end,int k)
{
if(begin==end) return begin;
int x=tree[tree[v].l].val-tree[tree[u].l].val; //得到属于区间[l,r]的数的个数
int mid=begin+end>>1;
if(x>=k) return qry(tree[u].l,tree[v].l,begin,mid,k);
else return qry(tree[u].r,tree[v].r,mid+1,end,k-x); //由于这里没有细心写对,所以错了~
}
int root[N];
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(b+1,b+1+n);
int d=unique(b+1,b+1+n)-b-1;
root[0]=build(1,1,d);
for(int i=1;i<=n;++i)
{
int pos=lower_bound(b+1,b+1+d,a[i])-b;
root[i]=update(root[i-1],1,d,pos,1);
}
while(m--)
{
int l,r,k;
scanf("%d %d %d",&l,&r,&k);
int pos=qry(root[l-1],root[r],1,d,k);
printf("%d\n",b[pos]);
}
}
类似题目:
Kth number
查找区间[s,t]第k大的数
#include <bits/stdc++.h>
using namespace std;
#define ms(a,x) memset(a,x,sizeof a)
const int N=1e5+5;
int T,n,m,a[N],b[N],root[N],idx;
struct Node{
int l,r,sum;
}tree[N<<5]; //开4倍会WA、TLE等
void build(int &id,int begin,int end)
{
id=++idx;
tree[id].sum=0;
if(begin==end) return;
int mid=begin+end>>1;
build(tree[id].l,begin,mid);
build(tree[id].r,mid+1,end);
}
void update(int pre,int &cur,int begin,int end,int pos)
{
//复制结点
cur=++idx;
tree[cur]=tree[pre];
tree[cur].sum++;
if(begin==end) return;
int mid=(begin+end)>>1;
if(pos<=mid) update(tree[pre].l,tree[cur].l,begin,mid,pos);
else update(tree[pre].r,tree[cur].r,mid+1,end,pos);
}
int qry(int u,int v,int begin,int end,int k)
{
if(begin==end) return begin;
int x=tree[tree[v].l].sum-tree[tree[u].l].sum;
int mid=begin+end>>1;
if(x>=k) return qry(tree[u].l,tree[v].l,begin,mid,k);
else return qry(tree[u].r,tree[v].r,mid+1,end,k-x);
}
int main()
{
scanf("%d",&T);
while(T--)
{
idx=0;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(b+1,b+1+n);
int d=unique(b+1,b+1+n)-b-1;
build(root[0],1,d); //建空树
for(int i=1;i<=n;++i)
{
int pos=lower_bound(b+1,b+1+d,a[i])-b;
update(root[i-1],root[i],1,d,pos);
}
int s,t,k;
while(m--)
{
scanf("%d %d %d",&s,&t,&k);
int pos=qry(root[s-1],root[t],1,d,k);
printf("%d\n",b[pos]);
}
}
}
/*
又涨知识了,全局变量ms可能会导致MLE,优化:只初始化需要的空间大小
*/