线段树(Segment Tree)

线段树

线段树是用一种树状结构来存储一个连续区间的信息的数据结构。

线段树的几点性质:
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));
			}
		}
	}
}

I Hate It

//单点修改,区间查询最大值

#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);
		}
	}
}

Can you answer these queries?

//区间修改,区间查询

区间修改每个数开平方的和,优化方法:当那个区间的数变为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';
}

*/

Atlantis

参考博客

//求矩形面积

#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);
	}
}
区间并

Tunnel Warfare

命令:
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开始,重建点,标记重建 
			}
		}
	}
}

Hotel

参考博客

区间修改,查询最左的连续区间的值。

#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);
			}
		}
	}
}

权值线段树

KiKi’s K-Number

线段树求第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)); 
			}
		}
	}
}

可持久化线段树

可持久化线段树 1(可持久化数组)

//单点修改,单点查询
理论学习:有了可持久化数组,便可以实现很多衍生的可持久化功能(例如:可持久化并查集)。

如题,你需要维护这样的一个长度为 N 的数组,支持如下几种操作

  1. 在某个历史版本上修改某一个位置上的值
  2. 访问某个历史版本上的某一位置的值

此外,每进行一次操作(对于操作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,优化:只初始化需要的空间大小
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值