线段树&树状数组&ST表 专题练习

线段树&树状数组&ST表 专题练习

前言

不要问我为什么拖更~~
只是因为还没AK——呸!!
其实是有一道传说中的
账号坟场 ——指纹!!
如果你使用了不正确的方法
——那么 你的二号
就会想你奔赴而来。
所以我也没有快速完成——怕封号!
好了,废话少说,赶快进入主题吧!

线段树

最大值

题目描述

在N(1<=N<=100000)个数A1…An组成的序列上进行M(1<=M<=100000)次操作,操作有两种:

(1)1 x y:表示修改A[x]为y;

(1)2 x y:询问x到y之间的最大值。

解析

线段树裸题,三种操作:建树、添加和查询(详见代码)

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5;
int a[MAXN+5],t[MAXN*4+20],n,m;
int l,r,px;
void build(int i,int x,int y)
{
	if(x==y)
	{
		t[i]=a[x];
		return;
	}
	int m=(x+y)/2;
	build(i*2,x,m);
	build(i*2+1,m+1,y);
	t[i]=max(t[i*2],t[i*2+1]);
}
void add(int i,int x,int y)
{
	if(x>r||y<l)return;
	if(x==y)
	{
		t[i]=px;
		return;
	}
	int m=(x+y)/2;
	add(i*2,x,m);
	add(i*2+1,m+1,y);
	t[i]=max(t[i*2],t[i*2+1]);
}
void ques(int i,int x,int y)
{
	if(x>r||y<l)return;
	if(x>=l&&y<=r)
	{
		px=max(px,t[i]);
		return;
	}
	int m=(x+y)/2;
	ques(i*2,x,m);
	ques(i*2+1,m+1,y);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	build(1,1,n);
	scanf("%d",&m);
	while(m--)
	{
		int op,oa,ob;
		scanf("%d%d%d",&op,&oa,&ob);
		if(op==1)
		{
			l=r=oa;px=ob;
			add(1,1,n);
		}
		else
		{
			l=oa,r=ob,px=-1e9;
			ques(1,1,n);
			printf("%d\n",px);
		}
	}
	return 0;
}

最大值(新版)

题目描述

在N(1<=N<=100000)个数A1…An组成的序列上进行M(1<=M<=100000)次操作,操作有两种:

(1)1 L R C:表示把A[L]到A[R]增加C(C的绝对值不超过10000);

(2)2 L R:询问A[L]到A[R]之间的最大值。

解析

线段树裸题+1,需要一个lazy数组表示还需要加的数,需要向下传递。

代码

#include<cstdio>
#include<algorithm>
#include<iostream>
const int N=1e5+5,MIN=-2e9;

int a[N],t[N*4],lz[N*4],bz;
int n,m,pl,pr,px;

int max(int x,int y){
	if(x>y)return x;
	else return y;
}
int min(int x,int y){
	if(x<y)return x;
	else return y;
}
void plus(int k,int x)
{
	lz[k]+=x;
	t[k]+=x;
}
void down(int x)
{
	plus(x*2,lz[x]);
	plus(x*2+1,lz[x]);
	lz[x]=0;
}
void bt(int i,int x,int y)
{
	if(x==y)
	{
		t[i]=a[x];
		return;
	}
	int m=(x+y)/2;
	bt(i+i,x,m);
	bt(i+i+1,m+1,y);
	t[i]=max(t[i+i],t[i+i+1]);
}
void add(int i,int x,int y)
{
	if(y<pl||x>pr)return;
	if(x>=pl&&y<=pr)
	{
		plus(i,px);
		return;
	}
	int m=(x+y)/2;
	add(i+i,x,m);
	down(i);
	add(i+i+1,m+1,y);
	t[i]=max(t[i+i],t[i+i+1]);
}
void qry(int i,int x,int y)
{
	if(y<pl||x>pr)return;
	if(x>=pl&&y<=pr)
	{
		px=max(px,t[i]);
		return;
	}
	int m=(x+y)/2;
	down(i);
	qry(i+i,x,m);
	qry(i+i+1,m+1,y);
}
int main()
{
	int x,y;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	bt(1,1,n);
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&bz,&x,&y);
		if(bz==1)
		{
			int z;scanf("%d",&z);
			pl=x,pr=y,px=z;
			add(1,1,n);
		}
		else
		{
			pl=x,pr=y,px=MIN;
			qry(1,1,n);
			printf("%d\n",px);
		}
	}
	return 0;
}

线段树

飞船(我也不知道为什么叫这个名字)

题目描述

和地面上看到的星星不同,在太空中看到的星星是成一条直线的,一共有N(1<=N<=100,000)颗星星,编号为1到N,每个星星有自己的体积,由于在飞船中很无聊,除了不停地玩弄手中失重的书和笔之外没有别的事可干,此时翟志刚说我们来玩游戏吧,一共玩了M轮(1<=M<=100,000),每一轮都是给出两个整数L和R(1<=L<=R<=N),询问第L到第R颗星星之间最大星星的体积。

解析

ST表(有点像dp),然后询问区间最值(需要利用到log2函数)

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5;
int a[MAXN],f[MAXN][20],ans=-1e9;
int b[20],n,m;
int max(int x,int y)
{
	return x>y?x:y;
}
int min(int x,int y)
{
	return x<y?x:y;
}
int question(int x,int y)
{
	int k=log2(y-x+1);
	return max(f[x][k],f[y-b[k]+1][k]);
}
int main()
{
	scanf("%d%d",&n,&m);
	b[0]=1;for(int i=1;i<=17;i++)b[i]=b[i-1]*2;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		f[i][0]=a[i];
	}
	for(int j=1;b[j]<=n;j++)
	{
		for(int i=1;i+b[j]-1<=n;i++)
		{
			f[i][j]=max(f[i][j-1],f[i+b[j-1]][j-1]);
		}
	}
	int l,r;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&l,&r);
		printf("%d\n",question(l,r));
	}
	return 0;
}

假期

题目描述

经过几个月辛勤的工作,FJ决定让奶牛放假。假期可以在1…N天内任意选择一段(需要连续),每一天都有一个享受指数W。但是奶牛的要求非常苛刻,假期不能短于P天,否则奶牛不能得到足够的休息;假期也不能超过Q天,否则奶牛会玩的腻烦。FJ想知道奶牛们能获得的最大享受指数。

解析

也是st表,但是初始化需要把所有的假期可能都列举再初始化

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5;
ll a[MAXN],f[MAXN][20],s[MAXN],ans=-1e9;
ll b[20],n,q,p;
ll max(ll x,ll y)
{
	return x>y?x:y;
}
ll min(ll x,ll y)
{
	return x<y?x:y;
}
ll question(ll x,ll y)
{
	int k=log2(y-x+1);
	return max(f[x][k],f[y-b[k]+1][k]);
}
int main()
{
	scanf("%lld%lld%lld",&n,&p,&q);
	b[0]=1;for(int i=1;i<=17;i++)b[i]=b[i-1]*2;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		s[i]=s[i-1]+a[i];
		f[i][0]=s[i];
	}
	for(int j=1;b[j]<=n;j++)
	{
		for(int i=1;i+b[j]-1<=n;i++)
		{
			f[i][j]=max(f[i][j-1],f[i+b[j-1]][j-1]);
		}
	}
	for(int i=1;i<=n-p+1;i++)
	{
		long long k=question(i+p-1,min(n,i+q-1));
		ans=max(ans,k-f[i-1][0]);
	}
	printf("%lld",ans);
	return 0;
}

与众不同

题目描述

A是某公司的CEO,每个月都会有员工把公司的盈利数据送给A,A是个与众不同的怪人,A不注重盈利还是亏本,而是喜欢研究“完美序列”:连续的互不相同的序列。A想知道区间[L,R]之间最长的完美序列。(一个奇怪的人)

解析

需要一个二分优化

代码

#include<cstdio>
#include<cmath>
using namespace std;
int n,m,a[200001],s[200001],f[200001][20],l[5000001],x,y;
int b[30]; 
int max(int x,int y) {return x>y?x:y;}
int find(int l,int r)
{
	int i=l,j=r;
	while(i<=j)
	{
		int mid=i+j>>1;
		if (s[mid]<l) i=mid+1;
		else j=mid-1;
	}
	return i;
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	for (int i=1;i<=n;i++)
	{
		a[i]+=1000000;
		s[i]=max(s[i-1],l[a[i]]+1);
		f[i][0]=i-s[i]+1;
		l[a[i]]=i;
	}
	b[0]=1;for(int i=1;i<30;i++)b[i]=b[i-1]*2;
	for (int j=1;j<=(int)log2(n);j++)
	{
		for (int i=1;i+b[j]-1<=n;i++)
		{
			f[i][j]=max(f[i][j-1],f[i+b[j-1]][j-1]);
		}
	}
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		x++;y++;
		int mid=find(x,y),k=(int)log2(y-mid+1);
		if (mid>y)
		{
			printf("%d\n",y-x+1);
		}
		else
		{
			printf("%d\n",max(mid-x,max(f[mid][k],f[y-b[k]+1][k])));
		}
	}
	return 0;
}

线段树

求和

题目描述

输入一个数列A1,A2….An(1<=N<=100000),在数列上进行M(1<=M<=100000)次操作,操作有以下两种:

(1) 格式为C I
X,其中C为字符“C”,I和X(1<=I<=N,|X|<=10000)都是整数,表示把把a[I]改为X

(2) 格式为Q L
R,其中Q为字符“Q”,L和R表示询问区间为L,R(1<=L<=R<=N),表示询问A[L]+…+A[R]的值。

解析

线段树都要建树和查询,还要用一个lowbit函数记录二进制最后一个1

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n;
int a[100005],c[100005],ans[100001];
int lowbit(int x)
{
	return x&(-x);
}
void updata(int sit,int ver)
{
	while(sit<=n)
	{
		c[sit]+=ver;
		sit+=lowbit(sit);
	}
}
int getsum(int r)
{
	int sum=0;
	while(r>0)
	{
		sum+=c[r];
		r-=lowbit(r);
	}
	return sum;
}
int main()
{
	int m,l=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		updata(i,a[i]);
	}
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		char k[3];
		int q1,q2;
		scanf("%s%d%d",k+1,&q1,&q2);
		if(k[1]=='Q')
		{
			ans[++l]=getsum(q2)-getsum(q1-1);
		}
		else
		{
			updata(q1,q2-a[q1]);
			a[q1]=q2;
		}
	}
	for(int i=1;i<=l;i++)
	{
		printf("%d\n",ans[i]);
	}
	return 0;
}

星星点灯

题目描述

天文学家经常要检查星星的地图,每个星星用平面上的一个点来表示,每个星星都有坐标。我们定义一个星星的“级别”为给定的星星中不高于它并且不在它右边的星星的数目。天文学家想知道每个星星的“级别”。

                        5

                      *

                    4

                  *

                1       2   3

              *       *   *

例如上图,5号星的“级别”是3(1,2,4这三个星星),2号星和4号星的“级别”为1。

给你一个地图,你的任务是算出每个星星的“级别”。

解析

裸题,y已经排序不考虑,注意函数要以最大(32000)为上限(血的教训)以及输入要加1,可能有值为0

代码

#include<bits/stdc++.h>
using namespace std;
const int MAXN=32005,MAX=-1e9,MIN=1e9;

int f[MAXN],n,m;
int lowbit(int x)
{
	return x&(-x);
}
void build(int x)
{
	while(x<=MAXN-4)
	{
		f[x]++;
		x+=lowbit(x);
	}
}
int ques(int k)
{
	int t=0;
	while(k>0)
	{
		t=t+f[k];
		k-=lowbit(k);
	}
	return t;
}
int main()
{
	scanf("%d",&n);
	int x,y;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&x,&y);
		printf("%d\n",ques(x+1));
		build(x+1);
	}
	return 0;
}

压轴(账号坟场)指纹

题目描述

由于指纹图像质量评估研究的需要,我们通过对一个人的指纹进行多次采样后得到多个不同质量的指纹图像,并将其各质量属性记录在一个数据库里面(不同图像的各属值均不相同)。对于两个指纹图像,单个属性的好坏并不能说明图像质量的高低。比如图像1 杂点数比图像2的少,但有可能图像1的粘连程度比图像2高得多,因此我们不能武断的认为图像1就比图像2好。

但是如果一个图像有不少于三个属性都优于另一个图像,那么我们有理由相信前者的质量要优于后者。对于数据库中的一个指纹图像I,如果存在另一个图像J , J 有不少于三个质量属性优于图像I,那么我们认为图像I是一种‘累赘’。

为了减少指纹图像数据库的大小,我们希望去除其中的累赘图像,现在实验室需要你的帮忙,找出该部分图像。为方便计算,我们已经分别按四个属性,计算出了所有图片按该属性的优劣程度排序的名次。

解析

真的不能暴力!!
可以用交换两组数据+树状数组来判断是否三组数据都比另一组更好
注意判断2、3、4数据时的交换方法

代码

#include<bits/stdc++.h>
#define min(u,v) u<v?u:v
#define lowbit(x) x&-x
const int N=1000005;
int a[N][5],f[N],c[N][5],s,n,bz[N];
const int inf=2147483647;
void sw(int u,int v) {
	int t;
	for(int i=1;i<=n;++i)
		t=a[i][u],a[i][u]=a[i][v],a[i][v]=t;
}
int query(int x) {
	int s=inf;
	while(x>0) {
		s=min(s,f[x]);
		x-=lowbit(x);
	}
	return s;
}
void add(int x,int y) {
	while(x<=n) {
		f[x]=min(f[x],y);
		x+=lowbit(x);
	}
}
void find()
{
	for(int i=1;i<=n;++i) f[i]=inf;
	for(int i=1;i<=n;++i) {
		if(bz[a[i][0]]==0) {
			if(query(a[i][2]-1)<a[i][3]) {
				bz[a[i][0]]=1;
				s++;
			}
		}
		add(a[i][2],a[i][3]);
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
		scanf("%d%d%d%d",&a[i][1],&a[i][2],&a[i][3],&a[i][4]),a[i][0]=i;
	for(int i=1;i<=n;++i)
		for(int j=0;j<=4;++j)
			c[a[i][1]][j]=a[i][j];
	for(int i=1;i<=n;++i)
		for(int j=0;j<=4;++j)
			a[i][j]=c[i][j];
	find();
	sw(3,4);
	find();
	sw(2,4);
	find();
	for(int i=1;i<=n;++i)
		for(int j=0;j<=4;++j)
			c[a[i][4]][j]=a[i][j];
	for(int i=1;i<=n;++i)
		for(int j=0;j<=4;++j)
			a[i][j]=c[i][j];
	find();
	printf("%d\n",s);
	for(int i=1;i<=1000000;++i)
		if(bz[i]==1) printf("%d\n",i);
}

后记

没什么好说的,只是真的不要暴力解题!!

另特别感谢 2021王 * 轩 提供的代码!!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值