数据结构线段树总结

P1937 [USACO10MAR] Barn Allocation G
所用算法:线段树:区间加法修改+求区间最小值
核心思路:贪心算法,优先满足右端点靠前的区间,每遍历一个区间我们就区间减1,每次检察是否有最小值=0的出现就不能再减了,这样就可以贪心的求出最多的区间数。

include<bits/stdc++.h>

using namespace std;
const int N=1e5+10;
int n,m;
struct Line{
int l,r;
bool operator<(const Line& l2)const{
return r < l2.r;
}
}line[N];
long long a[N];
long long tree[N<<2],tag[N<<2];
void pushup(int x)
{
tree[x]=min(tree[x<<1],tree[x<<1|1]);
}
void build(int root,int l,int r)
{
if(l==r)
{
tree[root]=a[l];
return ;
}
int mid=(l+r)/2;
build(root<<1,l,mid);
build(root<<1|1,mid+1,r);
pushup(root);
}
void pushdown(int x)
{
tree[x<<1]+=tag[x];
tree[x<<1|1]+=tag[x];
tag[x<<1]+=tag[x];
tag[x<<1|1]+=tag[x];
tag[x]=0;
}
int query(int x,int l,int r,int ql,int qr)
{
if(ql<=l&&qr>=r)
{
return tree[x];
}
int mid=(l+r)>>1;
int ans=0x3f3f3f3f;
pushdown(x);
if(ql<=mid)
{
ans=min(ans,query(x<<1,l,mid,ql,qr));
}
if(qr>mid)
{
ans=min(ans,query(x<<1|1,mid+1,r,ql,qr));
}
pushup(x);
return ans;
}
void update(int x,int l,int r,int ql,int qr,int value)
{
if(ql<=l&&qr>=r)
{
tree[x]+=value;
tag[x]+=value;
return;
}
int mid=(l+r)>>1;
pushdown(x);
if(ql<=mid)
{
update(x<<1,l,mid,ql,qr,value);
}
if(qr>mid)
{
update(x<<1|1,mid+1,r,ql,qr,value);
}
pushup(x);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
build(1,1,n);
for(int i=1;i<=m;i++)
{
cin>>line[i].l>>line[i].r;
}
sort(line+1,line+1+m);
long long res=0;
for(int i=1;i<=m;i++)
{
int l=line[i].l,r=line[i].r;
int minx=query(1,1,n,l,r);
if(minx>0)
{
res++;
update(1,1,n,l,r,-1);
}
}

cout<<res;
return 0;
}
P1972 [SDOI2009] HH 的项链
题目描述
HH 有一串由各种漂亮的贝壳组成的项链。HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。HH 不断地收集新的贝壳,因此,他的项链变得越来越长。

有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?这个问题很难回答……因为项链实在是太长了。于是,他只好求助睿智的你,来解决这个问题。

输入格式
第一行一个正整数 n,表示项链长度。
第二行 n 个正整数 a
i

,表示项链中第 i 个贝壳的种类。

第三行一个整数 m,表示 HH 询问的个数。
接下来 m 行,每行两个整数 l,r,表示询问的区间。

输出格式
输出 m 行,每行一个整数,依次表示询问对应的答案。
算法:树状数组:单点修改,求前缀和
核心思路:离线做法,先回答右端点最左边的区间,这样一来每个数只要看他目前最右边的那个就可以了。vis数组维护每个数最右边的位置,每当遍历到一个数时,如果他前面有就将原来的位置减一,现在的位置加一,如果没有还是现在的位置加1.这样一来前缀和=【1,i】的种类数,做前缀和的差就可以了。
项链是:1 3 4 5 1

那么,对于r=5的所有的询问来说,第一个位置上的1完全没有意义,因为r已经在第五个1的右边,对于任何查询的[L,5]区间来说,如果第一个1被算了,那么他完全可以用第五个1来替代。

因此,我们可以对所有查询的区间按照r来排序,然后再来维护一个树状数组,这个树状数组是用来干什么的呢?看下面的例子:

1 2 1 3

对于第一个1,insert(1,1);表示第一个位置出现了一个不一样的数字,此时树状数组所表示的每个位置上的数字(不是它本身的值而是它对应的每个位置上的数字)是:1 0 0 0

对于第二个2,insert(2,1);此时树状数组表示的每个数字是1 1 0 0

对于第三个1,因为之前出现过1了,因此首先把那个1所在的位置删掉insert(1,-1),然后在把它加进来insert(3,1)。此时每个数字是0 1 1 0

如果此时有一个询问[2,3],那么直接求sum(3)-sum(2-1)=2就是答案。

include

include

include

define rint register int

define maxn 1000010

using namespace std;

int n,m;
int a[maxn],ans[maxn];
int vis[maxn],tree[maxn];

struct QUE{
int l;
int r;
int id;
}q[maxn];

inline void read(int &x){
char ch=getchar();int f=1;x=0;
while(!isdigit(ch) && ch^'-') ch=getchar();
if(ch=='-') f=-1,ch=getchar();
while(isdigit(ch)) x=x10+ch-'0',ch=getchar();
x
=f;
}

inline bool cmp(const QUE &a,const QUE &b){
return a.r<b.r;
}

inline int lowbit(int x){
return x&(-x);
}

void modify(int p,int v){
for(;p<=n;p+=lowbit(p))
tree[p]+=v;
}

int query(int p){
int res=0;
for(;p;p-=lowbit(p))
res+=tree[p];
return res;
}

int main(){
read(n);
for(rint i=1;i<=n;++i) read(a[i]);
read(m);
for(rint i=1;i<=m;++i){
read(q[i].l); read(q[i].r); q[i].id=i;
}
sort(q+1,q+m+1,cmp);

int pow=1;
for(rint i=1;i<=m;++i){
for(rint j=pow;j<=q[i].r;++j){
if(vis[a[j]]) modify(vis[a[j]],-1);
modify(j,1);
vis[a[j]]=j;
}
pow=q[i].r+1;
ans[q[i].id]=query(q[i].r)-query(q[i].l-1);
}

for(rint i=1;i<=m;++i) printf("%d\n",ans[i]);
return 0;
}
P4344 [SHOI2015] 脑洞治疗仪
曾经发明了自动刷题机的发明家 SHTSC 又公开了他的新发明:脑洞治疗仪——一种可以治疗他因为发明而日益增大的脑洞的神秘装置。

为了简单起见,我们将大脑视作一个 01 序列。1 代表这个位置的脑组织正常工作,0 代表这是一块脑洞。

1 0 1 0 0 0 1 1 1 0

脑洞治疗仪修补某一块脑洞的基本工作原理就是将另一块连续区域挖出,将其中正常工作的脑组织填补在这块脑洞中。(所以脑洞治疗仪是脑洞的治疗仪?)

例如,用上面第 8 号位置到第 10 号位置去修补第 1 号位置到第 4 号位置的脑洞,我们就会得到:

1 1 1 1 0 0 1 0 0 0

如果再用第 1 号位置到第 4 号位置去修补第 8 号位置到第 10 号位置:

0 0 0 0 0 0 1 1 1 1

这是因为脑洞治疗仪会把多余出来的脑组织直接扔掉。

如果再用第 7 号位置到第 10 号位置去填补第 1 号位置到第 6 号位置:

1 1 1 1 0 0 0 0 0 0

这是因为如果新脑洞挖出来的脑组织不够多,脑洞治疗仪仅会尽量填补位置比较靠前的脑洞。

假定初始时 SHTSC 并没有脑洞,给出一些挖脑洞和脑洞治疗的操作序列,你需要即时回答 SHTSC 的问题:在大脑某个区间中最大的连续脑洞区域有多大。

输入格式
第一行两个整数 n,m,表示 SHTSC 的大脑可分为从 1 到 n 编号的 n 个连续区域,有 m 个操作。

以下 m 行每行是下列三种格式之一:

0lr:SHTSC 挖了一个范围为 [l,r] 的脑洞。
1l
0

r
0

l
1

r
1

:SHTSC 进行了一次脑洞治疗,用从 l
0

到 r
0

的脑组织修补 l
1

到 r
1

的脑洞。
2lr:SHTSC 询问 [l,r] 区间内最大的脑洞有多大。
上述区间均在 [1,n] 范围内。

输出格式
对于每个询问,输出一行一个整数,表示询问区间内最大连续脑洞区域有多大。
算法:核心重点:区间全赋值为0/1,求区间和,求区间连续的0的个数
核心思想:根据题意,挖脑洞填脑洞本质上是给一个区间全部赋值为0或者1,我们要求的是一个区间最大的连续最长的0区间,利用二分法我们找数目正好足够的位置,然后都赋值为0。

include <bits/stdc++.h>

define ls num<<1

define rs num<<1|1

using namespace std;
typedef long long ll;
templateinline void read(T &FF){
T RR=1;FF=0;char CH=getchar();
for(;!isdigit(CH);CH=getchar())if(CH'-')RR=-1;
for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
FF=RR;
}
templateinline void write(T x){
if(x<0)putchar('-'),x
=-1;
if(x>9)write(x/10);
putchar(x%10+48);
}
templateinline void writen(T x){
write(x);
puts("");
}
const int N=2e5+10;
struct Tree{
int l,r,lmax,rmax,sum,tag,len,ans;
}t[N<<2];
int n,m,l0,r0,l1,r1,f;
void pushup(int num){
t[num].sum=t[ls].sum+t[rs].sum;
if(t[ls].lmax
t[ls].len)t[num].lmax=t[ls].len+t[rs].lmax;
else t[num].lmax=t[ls].lmax;
if(t[rs].rmaxt[rs].len)t[num].rmax=t[rs].len+t[ls].rmax;
else t[num].rmax=t[rs].rmax;
t[num].ans=max(max(t[ls].ans,t[rs].ans),t[ls].rmax+t[rs].lmax);
}
void down1(int num){
t[num].ans=t[num].lmax=t[num].rmax=t[num].len;
t[num].sum=0;
t[num].tag=1;
}
void down2(int num){
t[num].ans=t[num].lmax=t[num].rmax=0;
t[num].sum=t[num].len;
t[num].tag=2;
}
void pushdown(int num){
if(t[num].tag
1){
down1(ls);down1(rs);
t[num].tag=0;
}
if(t[num].tag2){
down2(ls);down2(rs);
t[num].tag=0;
}
}
void build(int num,int l,int r){
t[num].tag=0;
t[num].l=l;
t[num].r=r;
t[num].len=r-l+1;
if(l
r){
t[num].sum=1;
t[num].ans=t[num].lmax=t[num].rmax=0;
return;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
pushup(num);
}
void change(int num,int x,int y,int z){
if(t[num].l>=x&&t[num].r<=y){
if(z1)down1(num);
if(z
2)down2(num);
return;
}
pushdown(num);
if(t[ls].r>=x)change(ls,x,y,z);
if(t[rs].l<=y)change(rs,x,y,z);
pushup(num);
}
int query0(int num,int x,int y){
if(t[num].l>=x&&t[num].r<=y)return t[num].sum;
pushdown(num);
if(t[ls].r<x)return query0(rs,x,y);
if(t[rs].l>y)return query0(ls,x,y);
return query0(ls,x,y)+query0(rs,x,y);
}
int query1(int num,int x,int y){
if(t[num].l>=x&&t[num].r<=y)return t[num].len-t[num].sum;
pushdown(num);
if(t[ls].r<x)return query1(rs,x,y);
if(t[rs].l>y)return query1(ls,x,y);
return query1(ls,x,y)+query1(rs,x,y);
}
void work(){
read(l1);read(r1);
int x=query0(1,l0,r0);
if(x==0)return;
change(1,l0,r0,1);
int l=l1,r=r1+1;
while(l+1<r){
int mid=(l+r)>>1;
if(query1(1,l1,mid)<=x)l=mid;
else r=mid;
}
change(1,l1,l,2);
}
int query2(int num,int x,int y){
if(t[num].l>=x&&t[num].r<=y)return t[num].ans;
pushdown(num);
if(t[ls].r<x)return query2(rs,x,y);
if(t[rs].l>y)return query2(ls,x,y);
return max(max(query2(ls,x,y),query2(rs,x,y)),min(t[ls].rmax,t[rs].l-x)+min(t[rs].lmax,y-t[ls].r));
}
int main(){
read(n);read(m);
build(1,1,n);
while(m--){
read(f);read(l0);read(r0);
switch(f){
case 0:change(1,l0,r0,1);break;
case 1:work();break;
case 2:writen(query2(1,l0,r0));break;
}
}
return 0;
}
P2824 [HEOI2016/TJOI2016] 排序
算法:区间赋值0/1,求区间和
核心思路:重要思想,如果是具体的数列排序就需要nlongn的时间段,这样就会很复杂,但是如果我们把>=某个数的数赋值为 1,<它的数赋值为0,这样就简单多了,利用线段树我们对于这个问题,我们使用线段树来维护。查询一段区间内的1的个数记为cnt1,如果是升序,就将这段区间的[r-cnt1+1, r]都更改为1,将[l, r-cnt1]更改为0。降序则将[l, l+cnt1-1]更改为1,将[l+cnt, r]更改为0。这样我们就成功地把排序转化为了区间查询和区间修改。这是一个离线的做法。首先二分答案mid。我们把原排列中大于等于mid的数都标记为1,小于mid的都标记为0。然后对于每个操作我们就将01序列排个序。最后如果第p个位子仍是1的话就是可行的。

include

include

include

define lc o<<1

define rc o<<1|1

define mid (l+r)/2

using namespace std;

const int N = 100010;
int n, m, p;
int T[4N], lazy[4N];//segment tree
int a[N], ch[N], L[N], R[N];//the information by reading

inline int read()
{
char ch = getchar(); int x = 0;
while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)){ x = x*10+ch-'0'; ch = getchar(); }
return x;
}

inline void build(int o, int l, int r, int x)
{
if (l == r){
T[o] = a[l] >= x;
lazy[o] = 0;
return;
}
build(lc, l, mid, x); build(rc, mid+1, r, x);
T[o] = T[lc]+T[rc]; lazy[o] = 0;
}

inline void pushdown(int o, int l, int r)
{
if (!lazy[o]) return;
lazy[lc] = lazy[rc] = lazy[o];
if (lazy[o] == 1){
T[lc] = mid-l+1; T[rc] = r-mid;
} else T[lc] = T[rc] = 0;
lazy[o] = 0;
}

inline int query(int o, int l, int r, int x, int y)
{
if (x <= l && y >= r) return T[o];
if (x > r || y < l) return 0;
pushdown(o, l, r);
return query(lc, l, mid, x, y) + query(rc, mid+1, r, x, y);
}

inline int queryPoint(int o, int l, int r, int x)
{
if (l == x && r == x) return T[o];
pushdown(o, l, r);
if (x <= mid) return queryPoint(lc, l, mid, x);
else return queryPoint(rc, mid+1, r, x);
}

inline void update(int o, int l, int r, int x, int y, int val)
{
if (x <= l && y >= r){
T[o] = val*(r-l+1); lazy[o] = val ? 1 : -1;
return;
}
if (x > r || y < l) return;
pushdown(o, l, r);
update(lc, l, mid, x, y, val);
update(rc, mid+1, r, x, y, val);
T[o] = T[lc]+T[rc];
}

inline bool check(int x)
{
build(1, 1, n, x);
for (int i = 1; i <= m; i ++){
int cnt1 = query(1, 1, n, L[i], R[i]);
if (ch[i] == 0){
update(1, 1, n, R[i]-cnt1+1, R[i], 1);
update(1, 1, n, L[i], R[i]-cnt1, 0);
}
else{
update(1, 1, n, L[i], L[i]+cnt1-1, 1);
update(1, 1, n, L[i]+cnt1, R[i], 0);
}
}
return queryPoint(1, 1, n, p);
}

int main()
{
n = read(); m = read();
for (int i = 1; i <= n; i ++) a[i] = read();
for (int i = 1; i <= m; i ++){
ch[i] = read(); L[i] = read(); R[i] = read();
}
p = read();
int ll = 1, rr = n, midd, ans;
while (ll <= rr){
midd = (ll+rr) >> 1;
if (check(midd)) ans = midd, ll = midd+1; else rr = midd-1;
}
printf("%d\n", ans);
return 0;
}
P1471 方差 题解
算法:区间加,区间求和
核心思路:同时维护区间平方和和区间和,注意顺序是先更新平方和再更新区间和,当成模板就行,注意细节

include

include

define lson rt<<1,l,mid

define rson rt<<1|1,mid+1,r

define Maxn 300010

using namespace std;
double sega[Maxn],segb[Maxn];
double mark[Maxn];
void pushup(int x)
{
sega[x]=sega[x<<1]+sega[x<<1|1];
segb[x]=segb[x<<1]+segb[x<<1|1];
}
void pushdown(int rt,int x)
{
if (mark[rt])
{
segb[rt<<1]+=2mark[rt]sega[rt<<1]+(x-x/2)mark[rt]mark[rt];
segb[rt<<1|1]+=2mark[rt]sega[rt<<1|1]+(x/2)mark[rt]mark[rt];
sega[rt<<1]+=(x-x/2)mark[rt];
sega[rt<<1|1]+=(x/2)
mark[rt];
mark[rt<<1]+=mark[rt];
mark[rt<<1|1]+=mark[rt];
mark[rt]=0;
}
}
void build(int rt,int l,int r)
{
if (lr)
cin>>sega[rt],segb[rt]=sega[rt]sega[rt];
else
{
int mid=(l+r)/2;
build(lson);
build(rson);
pushup(rt);
}
}
double query_a(int rt,int l,int r,int L,int R)
{
//--L--l--r--R--
if (l>=L && r<=R)
return sega[rt];
else
{
pushdown(rt,r-l+1);
int mid=(r+l)/2;
double ret=0;
if (mid>=L)
ret+=query_a(lson,L,R);
if (mid<R)
ret+=query_a(rson,L,R);
return ret;
}
}
double query_b(int rt,int l,int r,int L,int R)
{
//--L--l--r--R--
if (l>=L && r<=R)
return segb[rt];
else
{
pushdown(rt,r-l+1);
int mid=(r+l)/2;
double ret=0;
if (mid>=L)
ret+=query_b(lson,L,R);
if (mid<R)
ret+=query_b(rson,L,R);
return ret;
}
}
void update(int rt,int l,int r,int L,int R,double x)
{
if (l>=L && r<=R)
mark[rt]+=x,segb[rt]+=2
xsega[rt]+xx(r-l+1),sega[rt]+=(r-l+1)x;
else
{
pushdown(rt,r-l+1);
int mid=(r+l)/2;
if (mid>=L)
update(lson,L,R,x);
if (mid<R)
update(rson,L,R,x);
pushup(rt);
}
}
double sqr(double x)
{
return x*x;
}
main()
{
int n,m,x,y,c;
double z;
scanf("%d %d",&n,&m);
build(1,1,n);
for (int i=1;i<=m;i++)
{
scanf("%d",&c);
if (c
2)
scanf("%d%d",&x,&y),printf("%.4lf\n",query_a(1,1,n,x,y)/(y-x+1));
if (c1)
scanf("%d%d",&x,&y),cin>>z,update(1,1,n,x,y,z);
if (c
3)
{
scanf("%d%d",&x,&y);
double sum1=query_b(1,1,n,x,y)/(y-x+1),sum2=query_a(1,1,n,x,y)/(y-x+1);
double ans=sum1-sum2*sum2;
printf("%.4lf\n",ans);
}
}
}

posted @ 2026-05-30 08:47  曾翎一  阅读(9)  评论(0)    收藏  举报