牛客竞赛数据结构专题班树状数组、线段树练习题
牛客竞赛数据结构专题班树状数组、线段树练习题
笔者蒟蒻能力有限,能写几道写几道
[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