牛客竞赛数据结构专题班树状数组、线段树练习题

牛客竞赛数据结构专题班树状数组、线段树练习题

笔者蒟蒻能力有限,能写几道写几道

[NOIP2012]借教室

显然很符合线段树的操作,但是是区间和,还是区间最小值,还是区间最大值需要甄选

简化题意:求第几个操作后区间出现小于等于0,先输出-1,再输出第几个操作,如果操作完后都大于0,那么输出0

通过题意可以果断排除区间和和区间最大值,只需要维护区间最小值即可

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstring>
using namespace std;
#define int long long
int n,m;const int maxn=1e6+10;
int a[maxn];
int ans[maxn<<2],laz[maxn<<2];
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9' || ch<'0') if(ch=='-') f=-1;else ch=getchar();
	while(ch<='9' && ch>='0') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x*f;
}
void push_up(int p){
	ans[p]=min(ans[p<<1],ans[p<<1|1]);
}
void build(int p,int l,int r){
	if(l==r){
		ans[p]=a[l];
		return ;
	}int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	push_up(p);
}
void change(int p,int pf,int l,int r){
	ans[p]-=laz[pf];
	laz[p]+=laz[pf];
}
void push_down(int p,int l,int r){
	int mid=(l+r)>>1;
	change(p<<1,p,l,mid);
	change(p<<1|1,p,mid+1,r);
	laz[p]=0; 
} 
void update(int p,int l,int r,int nl,int nr,int k){
	if(nl<=l && r<=nr){
		ans[p]-=k;
		laz[p]+=k;return ;
	}
	push_down(p,l,r);
	int mid=(l+r)>>1;
	if(nl<=mid) update(p<<1,l,mid,nl,nr,k);
	if(nr>mid) update(p<<1|1,mid+1,r,nl,nr,k);
	push_up(p);
	return ;
}
bool query(int p,int l,int r,int nl,int nr){
	if(nl<=l && r<=nr) return ans[p]<0;
	int mid=(l+r)>>1;
	push_down(p,l,r);
	bool book=0;
	if(nl<=mid) book|=query(p<<1,l,mid,nl,nr);
	if(nr>mid) book|=query(p<<1|1,mid+1,r,nl,nr);
	return book;
}
signed main(){
	n=read();m=read();
	for(int i=1;i<=n;++i) a[i]=read();
	build(1,1,n);int book=0;
	for(int i=1;i<=m;++i){
		int k=read(),nl=read(),nr=read();
		if(book) continue;
		update(1,1,n,nl,nr,k);
		if(query(1,1,n,nl,nr)){
			puts("-1");
			printf("%d\n",i);book=i;
		}
	}
	if(!book) puts("0"); 
	return 0;
}

[SDOI2009]HH的项链

也是区间操作,统计的是区间内有多少个不同的数

其实很容易想到定义一个sum[i]表示前i个数中有多少个不同的数
那么答案易得 \(ans=sum[r]-sum[l-1]\)

没有要求在线处理,那么这种题可以考虑在线或离线处理

可以考虑离线做法,一般离线做法都会考虑对询问序列进行一系列的操作

怎么操作等下说

我们先来研究题面

举例
\(1,2,3,4,5,3,6\)
可以很直观的看出来\(sum[]={0,1,2,3,4,5,5,6}\)
当查询区间为[5,6]时,ans=sum[6]-sum[5-1]=1,显然不对

那又是什么原因造成这种错误,易发现[5,6]中只有3,5两个数字
sum[6]代表前6个数中共有5种数,sum[4]代表前4个数中有4种数,
好像没毛病

sum[6]-sum[4],减去是两个区间内共有的数的种数,但是带入原序列中会发现我们减去3这个数,但[5,6]中却有这个数
所以当遇到所求区间5,6和减去区间[1,4](即sum[4])出现共同的数的时候不可这么做
为赋予原有的意义
所以我们进行sum[6]-sum[4]之前,sum[4]应该减去[4,6]中的共有的数的种数,sum[4]-1=3,而此时sum[6]-sum[4]=2

归纳上述过程

查询某个区间[l,r]时,我们设[1,l-1]中的有x种数与[l,r]中的数相同,\(sum[r]-(sum[l-1]-x)=ans\),如果x=0,说明没有出现重复的共同数

我们讨论完单个重复情况

如果有多个重复情况了?(其实也可以再手动模拟下出现两种数相同的情况)

受到单个情况讨论的启发,我们可以将按r进行排序,然后,从上一个r'枚举到目前这个r,如果遇到个数之前出现过\(sum[p]=sum[p]-1\)(p为上次这个数出现的位置)

枚举到这个r之后重新记录这个数的位置,然后ans=sum[r]-sum[l-1]

实现时略有不同

你会发现sum[]是动态变化的,所以用树状数组即时求出sum[r]和sum[l-1]即可

然后sum[p]=sum[p]-1(add(p,-1)),会影响后面的数求和,所以sum[p'](p'为另一个与它相同的却在它后面的数)(add(p',1))

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;
#define lowbit(x) x&-x;
int n,m;
const int maxn=1e6+10;
int t[maxn],a[maxn];
int pre[maxn];//记录某个数上次出现的位置
int ans[maxn];
struct node{
	int l,r,p;
}ask[maxn]; 
int read(){
	int x=0;char ch=getchar();
	while(ch<'0' || ch>'9') ch=getchar();
	while(ch>='0' && ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); 
	return x; 
}
bool cmp(node a,node b){
	return a.r<b.r;
}
void add(int x,int val){//加上val
	while(x<=n){
		t[x]+=val;
 		x+=lowbit(x);
	}
} 
int sum(int x){//求1~x和
	int res=0;
	while(x){
		res+=t[x];
		x-=lowbit(x);
	}return res;
}

int main(){
	n=read();
	for(int i=1;i<=n;++i) a[i]=read();
	m=read();
	for(int i=1;i<=m;++i) ask[i].l=read(),ask[i].r=read(),ask[i].p=i;//读入
	sort(ask+1,ask+1+m,cmp);//按r排序
	int nex=1;
	for(int i=1;i<=m;++i){
		for(int j=nex;j<=ask[i].r;++j){
			if(pre[a[j]]) add(pre[a[j]],-1);//这个数之前出现过,减去
			add(j,1);//重新计算
			pre[a[j]]=j;//记录这个数最近出现的位置
		}
		nex=ask[i].r+1;//更新为当前这个r的下一位
		ans[ask[i].p]=sum(ask[i].r)-sum(ask[i].l-1);//这个询问的答案
	}
	for(int i=1;i<=m;++i) cout<<ans[i]<<endl;
	return 0;
}

ZFY AK IOI

posted @ 2021-08-28 14:27  归游  阅读(138)  评论(0编辑  收藏  举报