树状数组 + 线段树

线段树与树状数组及其在不同场合的使用 线段树 树状数组 首先讲解了线段树,然后讲解树状数组,最后比较了树状数组和线段树的用法。 立即下载

目录

引入知识:

一、树状数组

1. 可以解决的操作

 2. 基本原理

3. 基本应用

        1)Acwing --- 241 - 楼兰图腾

代码如下:

2)Acwing --- 242 - 一个简单的整数问题(区间修改+单点查询)

代码如下:

3)Acwing --- 243 - 一个简单的整数问题2(区间修改+区间查询)

思路:

代码如下:

4)Acwing --- 244 - 谜一样的牛

        思路:

代码如下:

二、线段树(一)

1.最简单的线段树的四种基本操作

2.原理

3. 应用

1)Acwing --- 1275 - 最大数

思路:

代码如下:

2)Acwing --- 245 - 你能回答这些问题吗

思路:

代码如下:

3)Acwing --- 246 - 区间最大公约数

代码如下:

三、线段树(二)

1) Acwing --- 243 - 一个简单的整数问题2(区间修改+区间查询)

代码如下:

2)Acwing --- 247 - 亚特兰蒂斯

思路:

代码如下:

2)Acwing --- 1277 - 维护序列

代码如下:


引入知识:

        lowbit操作,原理:

代码实现:

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

一、树状数组

1. 可以解决的操作

        1) 快速求前缀和 -O(logn)

        2) 修改某一个数 -O(logn)

一般情况下,是否用树状数组做某个题,就是看是不是只有这两种操作,否则做不了。

        注意:树状数组只能解决区间求和问题,解决不了最值问题。

 2. 基本原理

      可以参考: 树状数组 - OI Wiki

        基于二进制实现:

      假设 x=2 ^ i [k] + 2 ^ i [k - 1] + 2 ^ i [k - 2] + ... + 2 ^ i [1] , i [k] >= i [k - 1] >= i [k - 2] >= ... >=i[1]

      我们可以将 x 划分为以下 k 个区间:

   ( 1 ) : [ x - 2 ^ i [1] , x ]                                                                     包含2 ^ i [1]个数

   ( 2 ) : [ x - 2 ^ i [2] - 2 ^ i [1] , x - 2 ^ i [1] ]                                        包含2 ^ i [2]个数

        ........

   ( k ) : [ x , x - 2 ^ i [k - 1] - 2 ^ i [k - 2] - ... - 2 ^ i [1] ]                        包含2 ^ i [k]个数

        观察右端点,我们可以发现区间( L,R ]长度一定是R的二进制最后一位表示的最后一位1所对应的次幂,所以区间可以表示为 [ R-lowbit(R)+1 , R ] ;

令C[x] =a [ x-lowbit(x)+1 , x ]

        接下来,观察C[ x ]( 表示区间[ x-lowbit(x)+1 , x ]中所有数的和  ),不难看出1~x所有数的和最多可以划分为log(x)个区间,因此算总和可以用log(x)的时间复杂度。

假设原数组a:1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16

则c[ 1 ]=1 , c[ 3 ]=3 , c[ 5 ]=5 , c[ 7 ]=7 ,  c[ 9 ]=9 , c[ 11 ]=11 , c[ 13 ]=13 , c[ 15 ]=15 , 

c[ 2 ]=sum{ 1 , 2} , c[ 4 ]=sum{ 1 , 2 , 3 , 4 } , c[ 6 ]=sum{ 5 , 6 },

c[ 8 ]=sum{ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 } , c[ 10 ]=sum{ 9 , 10 },c[ 12 ]=sum{9 , 10 , 11 , 12}

c[ 14 ]=sum{ 13 , 14 }

c[ 16 ]=sum{ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 }

如下图所示:

初始化:for(int i=1;i<=n;i++)add(i,a[i]);        //O(logn)

        假设x=......10000 ,则C[ x ]=以x结尾,长度为2^4的区间和,求[ x-2^4+1,x ] 区间和,则 第一段区间:( ......01110 , ......01111 ] ,

        第二段区间:( ......01100 , ......01110 ] ,

        第三段区间:( ......01000 , ......01100 ] ,

        第四段区间:( ......00000 , ......01000 ] ,

即 C[ x ]=a[ x ]+C[ 15 ]+C[ 14 ]+C[ 12 ]+C[ 8 ]。

        C [ i ]:表示已 i 结尾的长度为lowbit(i)的区间和。

        修改操作:修改的过程是每次在二进制的最后的1加上1。

        假设修改a[ 7 ] , 则C[ 7 ]、C[ 8 ]、C[ 16 ]都会改变。

因此,假设a[ x ]+= y , 则for(int i=x;i <= n;i += lowbit(i)) C[ i ] += y ;

        查询操作(求1~x的和):查询的过程是每次去掉二进制的最后1位1(lowbit 操作)。

for(int i=x; i ;i -= lowbit(i)) C[ i ] += y;

3. 基本应用

--- 区间修改+单点查询

--- 区间修改+区间查询

--- 二维区间修改+区间查询

--- 偏序问题(逆序对+离散化)

---区间最值

        1)Acwing --- 241 - 楼兰图腾

        题目来源:https://www.acwing.com/problem/content/243/

问题描述:

代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int N=2e5+10;
int n;
int a[N],tr[N];//a表示原数组,tr表示树状数组
int Greater[N],lower[N];//Greater[k]:1~k-1中比a[k]大的数的个数,lower[k]1~k-1中比a[k]小的数的个数
int lowbit(int x){
    return x&-x;
}
void add(int x,int c){//修改
    for(int i=x;i<=n;i+=lowbit(i))tr[i]+=c;
}
int sum(int x){//查询前缀和
    int res=0;
    for(int i=x;i>0;i-=lowbit(i))res+=tr[i];
    return res;
}
void solve(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++){
        int y=a[i];
        Greater[i]=sum(n)-sum(y);//1~y-1中有多少个大于y的数
        lower[i]=sum(y-1);//1~y-1中有多少个小于y的数
        add(y,1);//把这个数加进树状数组中,表示数字y出现次数多1
    }
    memset(tr,0,sizeof tr);
    ll res1=0,res2=0;
    for(int i=n;i>0;i--){
        int y=a[i];
        res1+=Greater[i]*(ll)(sum(n)-sum(y));//左边比y大的数*y+1~n中比y大的数
        res2+=lower[i]*(ll)sum(y-1);//
        add(y,1);//把这个数加进树状数组中,表示数字y出现次数多1
    }
    cout<<res1<<" "<<res2<<endl;
}
int main(){
    int t;
    t=1;
    //cin>>t;
    while(t--){
        solve();
    }
    return 0;
}

2)Acwing --- 242 - 一个简单的整数问题(区间修改+单点查询)

        原题链接:https://www.acwing.com/problem/content/248/

题目描述:

代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n,m;
int a[N];
ll tr[N];
int lowbit(int x){
    return x&-x;
}
void add(int x,int c){
    for(int i=x;i<=n;i+=lowbit(i))tr[i]+=c;
}
ll sum(int x){
    ll res=0;
    for(int i=x;i;i-=lowbit(i))res+=tr[i];
    return res;
}
void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i],add(i,a[i]-a[i-1]);
    while(m--){
        char op[2];
        int l,r,d;
        cin>>op>>l;
        if(op[0]=='C'){
            cin>>r>>d;
            add(l,d),add(r+1,-d);    //差分
        }else{
            cout<<sum(l)<<endl;
        }
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    t=1;
    //cin>>t;
    while(t--){
        solve();
    }
    return 0;
}

3)Acwing --- 243 - 一个简单的整数问题2(区间修改+区间查询)

        原题链接:https://acwing.com/problem/content/244/

问题描述:

思路:

        1. 原数组a变成差分数组b---求a[l~r]+c ---> b[ l ] += c ; b[ r + 1 ] -= c ;

        2. 求S=a[1]+...+a[x]

        for(int i=1;i<=x;i++)

                for(int j=1;j<=i;j++)

                        S += b[j];

代码如下:
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=1e5+10;
ll n,m;
ll a[N];
ll tr1[N],tr2[N];//tr[1]:维护差分数组b[i]的前缀和,tr2[i]维护b[i]*i的前缀和

ll lowbit(ll x){
    return x&-x;
}
void add(ll tr[],ll x,ll c){
    for(int i=x;i<=n;i+=lowbit(i))tr[i]+=c;
}
ll sum(ll tr[],ll x){
    ll res=0;
    for(int i=x;i;i-=lowbit(i))res+=tr[i];
    return res;
}
ll prefix_sum(ll x){
    return sum(tr1,x)*(x+1)-sum(tr2,x);
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++){
        ll b=a[i]-a[i-1];
        add(tr1,i,b);
        add(tr2,i,(ll)b*i);
    }
    while(m--){
        char op[2];
        ll l,r,d;
        cin>>op>>l>>r;
        if(op[0]=='Q'){
            cout<<prefix_sum(r)-prefix_sum(l-1)<<endl;
        }else{
            cin>>d;;
            add(tr1,l,d),add(tr2,l,l*d);
            add(tr1,r+1,-d),add(tr2,r+1,-(r+1)*d);
        }
    }
}

4)Acwing --- 244 - 谜一样的牛

        原题链接:https://www.acwing.com/problem/content/245/

题目描述:

        思路:

        假设有n头牛,第 i 头牛前面有a[ i ]头牛比第 i 头牛矮的牛,则第 i 头牛应该是剩下的牛中排行第a[ i ]+1的牛

         1.从剩余的数中,找出第 k 小的数 <--->找到最小的一个x,使得sum(x)=k(sum(x)表示1~x中剩余多少个数)    2. 删除某个数

首先,a[1]~a[n]=1,表示每个身高可以用一次,然后用树状数组维护a[1]~a[n]的前缀和。

代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n;
int h[N],ans[N],tr[N];
int lowbit(int x){
    return x&-x;
}
void add(int x,int c){
    for(int i=x;i<=n;i+=lowbit(i))tr[i]+=c;
}
int sum(int x){
    int res=0;
    for(int i=x;i;i-=lowbit(i))res+=tr[i];
    return res;
}
int main(){
    cin>>n;
    h[1]=0;
    for(int i=2;i<=n;i++)cin>>h[i];
    
    for(int i=1;i<=n;i++)tr[i]=lowbit(i);
    for(int i=n;i;i--){
        int k=h[i]+1;
        int l=1,r=n;
        while(l<r){
            int mid=l+r>>1;
            if(sum(mid)>=k)r=mid;
            else l=mid+1;
        }
        ans[i]=r;
        add(r,-1);
    }
    for(int i=1;i<=n;i++)cout<<ans[i]<<endl;
}

二、线段树(一)

1.最简单的线段树的四种基本操作

        1)push up(u):由子节点算父节点的信息

        2)build ():将一段区间初始化成线段树

        3)modify():修改某个点或者区间---1. 修改单点   2. 修改区间 push down(又称懒标记或延迟标记):把当前父节点的修改信息更新到子节点

        4)query()

2.原理

线段树基础 - OI Wiki

除最后一层外,是满二叉树 ---> 用一维数组存储

编号是x的点:父节点编号是 x/2(x>>1) 下取整,左儿子编号是 2x(x<<1),右儿子编号是 2x+1(x<<1|1)。

整棵树有n个叶子结点,除了最后一层一共2n-1个点,最坏情况下,最后一层是倒数第二层的两倍,因此最坏情况下,整棵树有4n-1个结点。 

build(int u,int l,int r){

        tr[n].l=l,tr[n.r=r;

        if(l==r)return ;

        int mid=l+r>>1;

        build(u<<1,l,mid),build(u<<1|1,mid+1,r);

}

3. 应用

1)Acwing --- 1275 - 最大数

问题描述:

给定一个正整数数列 a1,a2,…,an,每一个数都在 0∼p−1 之间。

可以对这列数进行两种操作:

        1. 添加操作:向序列后添加一个数,序列长度变成 n+1;
        2. 询问操作:询问这个序列中最后 L 个数中最大的数是多少。
程序运行的最开始,整数序列为空。

写一个程序,读入操作的序列,并输出询问操作的答案。

输入格式
第一行有两个正整数 m,p,意义如题目描述;

接下来 m 行,每一行表示一个操作。

如果该行的内容是 Q L,则表示这个操作是询问序列中最后 L 个数的最大数是多少;

如果是 A t,则表示向序列后面加一个数,加入的数是 (t+a) mod p。其中,t 是输入的参数,a 是在这个添加操作之前最后一个询问操作的答案(如果之前没有询问操作,则 a=0)。

第一个操作一定是添加操作。对于询问操作,L>0 且不超过当前序列的长度。

输出格式
对于每一个询问操作,输出一行。该行只有一个数,即序列中最后 L 个数的最大数。

数据范围
1≤m≤2×105,
1≤p≤2×109,
0≤t<p

输入样例

10 100
A 97
Q 1
Q 1
A 17
Q 2
A 63
Q 1
Q 1
Q 3
A 99

输出样例

97
97
97
60
60
97

思路:

        1. 在某个位置修改一个数

        2. 在某一个区间内的最大值

struct Node{

        int l,r;    //左右端点

        int v;        //记录区间内的最大值    //一般存某个区间的某种属性(看这个属性能不能由两个子区间的属性算出来,不能的话,再存一些其他属性)

}

代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
int m,p;
struct Node{
	int l,r;
	int v;//区间l~r的最大值
}tr[N*4];
void pushup(int u){//由子节点的信息,来计算父节点的信息
	tr[u].v=max(tr[u<<1].v,tr[u<<1|1].v);
}
void build(int u,int l,int r){
	tr[u]={l,r};
	if(l==r)return ;
	int mid=(l+r)>>1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
}
int query(int u,int l,int r){
	if(tr[u].l>=l&&tr[u].r<=r)return tr[u].v;//树中节点已经被完全包含在[l,r]中了
	int mid=tr[u].l+tr[u].r>>1;
	int v=0;
	if(l<=mid)v=query(u<<1,l,r);
	if(r>mid)v=max(v,query(u<<1|1,l,r));
	return v;
}
void modify(int u,int x,int v){
	if(tr[u].l==x&&tr[u].r==x)tr[u].v=v;
	else{
		int mid=tr[u].l+tr[u].r>>1;
		if(x<=mid)modify(u<<1,x,v);
		else modify(u<<1|1,x,v);
		pushup(u);
	}
}
void solve(){
    int n=0,a=0;//n表示当前数据的个数
	cin>>m>>p;
    build(1,1,m);
    char op[2];
    int x;
	while(m--){
		cin>>op>>x;
		if(op[0]=='Q'){
			a=query(1,n-x+1,n);
			cout<<a<<endl;
		}else{
			modify(1,n+1,(a+x)%p);
			n++;
		}
	}
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    t=1;
    //cin>>t;
    while(t--){
        solve();
    }
    return 0;
}

2)Acwing --- 245 - 你能回答这些问题吗

原题链接:https://www.acwing.com/problem/content/246/

        问题描述:

思路:

1. 单点修改

2. 查询区间内的最大字段和

struct Node{

        int l,r;        //区间左右端点

        int tmax;        //最大连续字段和

        int lmax;        //最大前缀和

        int rmax;        //最大后缀和

        int sum;        //区间和

}

tmax=max(左儿子的tamx,右儿子的tmax,左儿子的rmax+右儿子的lmax);

代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+10;
int n,m;
int w[N];
struct Node{
    int l,r;
    int sum,lmax,rmax,tmax;
}tr[N*4];
void pushup(Node &u,Node &l,Node &r){//u是l和r的父节点
    u.sum=l.sum+r.sum;
    u.lmax=max(l.lmax,l.sum+r.lmax);
    u.rmax=max(r.rmax,r.sum+l.rmax);
    u.tmax=max(max(l.tmax,r.tmax),l.rmax+r.lmax);
}
void pushup(int u){
    pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
    if(l==r)tr[u]={l,r,w[r],w[r],w[r],w[r]};
    else{
        tr[u]={l,r};
        int mid=l+r>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        pushup(u);
    }
}
int modify(int u,int x,int v){
    if(tr[u].l==x&&tr[u].r==x)tr[u]={x,x,v,v,v,v};
    else{
        int mid=tr[u].l+tr[u].r>>1;
        if(x<=mid)modify(u<<1,x,v);
        else modify(u<<1|1,x,v);
        pushup(u);
    }
}
Node query(int u,int l,int r){
    if(tr[u].l>=l&&tr[u].r<=r)return tr[u];
    else{
        int mid=tr[u].l+tr[u].r>>1;
        if(r<=mid)return query(u<<1,l,r);
        else if(l>mid)return query(u<<1|1,l,r);
        else{
            auto left=query(u<<1,l,r);
            auto right=query(u<<1|1,l,r);
            Node res;
            pushup(res,left,right);
            return res;
        }
    }
}
void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>w[i];
    build(1,1,n);
    int k,x,y;
    while(m--){
        cin>>k>>x>>y;
        if(k==1){
            if(x>y)swap(x,y);
            cout<<query(1,x,y).tmax<<endl;
        }else modify(1,x,y);
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    t=1;
    //cin>>t;
    while(t--){
        solve();
    }
    return 0;
}

3)Acwing --- 246 - 区间最大公约数

原题链接:https://www.acwing.com/problem/content/247/

        问题描述:

代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+10;
int n,m;
ll w[N];
struct Node{
    int l,r;
    ll sum,d;
}tr[N*4];
ll gcd(ll a,ll b){
    return b?gcd(b,a%b):a;
}
void pushup(Node &u,Node &l,Node &r){
    u.sum=l.sum+r.sum;
    u.d=gcd(l.d,r.d);
}
void pushup(int u){
    pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
    if(l==r){
        ll b=w[r]-w[r-1];
        tr[u]={l,r,b,b};
    }else{
        tr[u]={l,r};
        int mid=l+r>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        pushup(u);
    }
}
void modify(int u,int x,ll v){
    if(tr[u].l==x&&tr[u].r==x){
        ll b=tr[u].sum+v;
        tr[u]={x,x,b,b};
    }else{
        
        int mid=tr[u].l+tr[u].r>>1;
        if(x<=mid)modify(u<<1,x,v);
        else modify(u<<1|1,x,v);
        pushup(u);
    }
}
Node query(int u,int l,int r){
    //if(l>r)return {0};
    if(tr[u].l>=l&&tr[u].r<=r)return tr[u];
    else{
        int mid=tr[u].l+tr[u].r>>1;
        if(r<=mid)return query(u<<1,l,r);
        else if(l>mid)return query(u<<1|1,l,r);
        else{
            auto left=query(u<<1,l,r),right=query(u<<1|1,l,r);
            Node res;
            pushup(res,left,right);
            return res;
        }
    }
}
void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>w[i];
    build(1,1,n);
    int l,r;
    char op[2];
    ll d;
    while(m--){
        cin>>op>>l>>r;
        if(op[0]=='Q'){
            auto left=query(1,1,l);
            Node right={0,0,0,0};
            if(l+1<=r)right=query(1,l+1,r);
            cout<<abs(gcd(left.sum,right.d))<<endl;
        }else{
            cin>>d;
            modify(1,l,d);
            if(r+1<=n)modify(1,r+1,-d);
        }
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    t=1;
    //cin>>t;
    while(t--){
        solve();
    }
    return 0;
}

三、线段树(二)

懒标记

add表示:给以当前结点为根的子树中的每一个结点加上add(不包含根结点)

pushdown操作:

        left.add+=root.add;left.sum+(left.r-left.l)*root.add;

        right.add+=root.add;right.sum+(right.r-right.l)*root.add;

        root.add=0;

1) Acwing --- 243 - 一个简单的整数问题2(区间修改+区间查询)

        原题链接:https://acwing.com/problem/content/244/

问题描述:

代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n,m;
int w[N];
struct Node{
    int l,r;
    ll sum,add;
}tr[N*4];
void pushup(int u){
    tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int u){
    auto &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
    if(root.add){
        left.add+=root.add,left.sum+=(ll)(left.r-left.l+1)*root.add;
        right.add+=root.add,right.sum+=(ll)(right.r-right.l+1)*root.add;
        root.add=0;
    }
}
void build(int u,int l,int r){
    if(l==r)tr[u]={l,r,w[r],0};
    else{
        tr[u]={l,r};
        int mid=l+r>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        pushup(u);
    }
}
void modify(int u,int l,int r,int d){
    if(tr[u].l>=l&&tr[u].r<=r){
        tr[u].sum+=(ll)(tr[u].r-tr[u].l+1)*d;
        tr[u].add+=d;
    }else{
        pushdown(u);
        int mid=tr[u].l+tr[u].r>>1;
        if(l<=mid)modify(u<<1,l,r,d);
        if(r>mid)modify(u<<1|1,l,r,d);
        pushup(u);
    }
}
ll query(int u,int l,int r){
    if(tr[u].l>=l&&tr[u].r<=r)return tr[u].sum;
    pushdown(u);
    int mid=tr[u].l+tr[u].r>>1;
    ll sum=0;
    if(l<=mid)sum+=query(u<<1,l,r);
    if(r>mid)sum+=query(u<<1|1,l,r);
    pushup(u);
    return sum;
}
void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>w[i];
    build(1,1,n);
    char op[2];
    int l,r,d;
    while(m--){
        cin>>op>>l>>r;
        if(op[0]=='C'){
            cin>>d;
            modify(1,l,r,d);
        }else{
            cout<<query(1,l,r)<<endl;
        }
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t=1;
    //cin>>t;
    while(t--)solve();
    return 0;
}

2)Acwing --- 247 - 亚特兰蒂斯

问题描述:

思路:

将垂直x轴的边画一条直线,如下图:

面积为:

S=h[1]*(x[2]-x[1]) + h[2]*(x[3]-x[2]) + ... +h[i]*(x[i+1]-x[i])

将每个矩形的左右两条边做成带权值的边,如图:

将y轴做成线段树,将每个带权值的线段所对应y轴的区间+对应边的权值

操作一:将某个区间[ l , r ] + k          操作二:整个区间中长度大于0的区间总长是多少

cnt:表示当前区间整个被覆盖的次数       

len:不考虑祖先结点cnt的前提下(永远只往下看),cnt>0的区间总长

扫描线性质:

1. 永远只考虑根节点信息        ---说明query时,不会用到pushdown

2. 所有的操作均是成对出现且先加后减        ---modify时,也不会用到pushdown

代码如下:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 100010;

struct segment// 用来存线段信息
{
    double x, y1,y2;
    int d; // 区分它是该矩阵前面的线段还是后面的线段
    bool operator < (const segment&t)const
    {
        return x < t.x;
     } 
}seg[N * 2];//每个矩阵需要存两个线段
struct node
{
    int l,r;
    int cnt;// 记录这段区间出现了几次
    double len;// 记录这段区间的长度;即线段长度
}tr[N * 8];//由于线段二倍,所以8倍空间
vector<double>ys;//用于离散化
int n;

int find(double y)
{
    // 需要返回vector 中第一个 >= y 的数的下标
    return lower_bound(ys.begin(), ys.end(), y) - ys.begin();
}

void pushup(int u)
{
    if(tr[u].cnt)tr[u].len = ys[tr[u].r + 1] - ys[tr[u].l];//表示整个区间都被覆盖,该段长度就为右端点 + 1后在ys中的值 - 左端点在ys中的值
   
    else if(tr[u].l != tr[u].r)tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
    else tr[u].len = 0;//表示为叶子节点且该线段没被覆盖,为无用线段,长度变为0
}

void modify(int u,int l,int r,int d)//表示从线段树中l点到r点的出现次数 + d
{
    if(tr[u].l >= l && tr[u].r <= r)//该区间被完全覆盖
    {
        tr[u].cnt += d;//该区间出现次数 + d
        pushup(u);//更新该节点的len
    }
    else
    {
        int mid = tr[u].r + tr[u].l >> 1;
        if (l <= mid)modify(u << 1,l,r,d);//左边存在点
        if (r > mid)modify(u << 1 | 1,l,r,d);//右边存在点
        pushup(u);//进行更新
    }
}

void build(int u,int l,int r)
{
    tr[u] = {l,r,0,0};

    if (l != r)
    {
        int mid = l + r >> 1;
        build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
        //后面都为0,不需更新len
    }
}

int main()
{
    int T = 1;
    while (cin>>n,n)
    {
        ys.clear();
        int j = 0;//一共j个线段
        for (int i = 0 ; i < n ; i ++)//处理输入
        {
            double x1,y1,x2,y2;
            cin>>x1>>y1>>x2>>y2;
            seg[j ++] = {x1,y1,y2,1};//前面的线段
            seg[j ++] = {x2,y1,y2,-1};//后面的线段
            ys.push_back(y1),ys.push_back(y2);//y轴出现过那些点
        }
        sort(seg,seg + j);//线段按x排序

        sort(ys.begin(),ys.end());//先排序
        ys.erase(unique(ys.begin(),ys.end()),ys.end());//离散化去重
        build(1,0,ys.size() - 2);

        double res = 0;

        for (int i = 0 ; i < j ; i ++)
        {
            //根节点的长度即为此时有效线段长度 ,再 * x轴长度即为面积
            if (i)res += tr[1].len * (seg[i].x - seg[i - 1].x);
            //处理一下该线段的信息,是加上该线段还是消去
            modify(1,find(seg[i].y1), find(seg[i].y2) - 1,seg[i].d);
        }

        printf("Test case #%d\n", T ++ );
        printf("Total explored area: %.2lf\n\n", res);
    }
    return 0;
} 

2)Acwing --- 1277 - 维护序列

代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int t;
int m, n, p;
int w[N];
struct node
{
	int l, r;
	ll sum, add, mul;
}tr[N*4];
void pushup(int u)
{
	tr[u].sum = (tr[u << 1].sum + tr[u << 1 | 1].sum) % p;
}
void change(node &now,ll add,ll mul)
{
	now.sum = (now.sum*mul + (ll)(now.r - now.l + 1)*add) % p;
	now.mul = now.mul*mul%p;
	now.add = (now.add*mul + add) % p;
}
void pushdown(int u)//向下更新
{
	change(tr[u << 1], tr[u].add, tr[u].mul);
	change(tr[u << 1 | 1], tr[u].add, tr[u].mul);
	tr[u].add = 0, tr[u].mul = 1;//往下传后,当前层清空懒标记
}
void build(int u, int l, int r)
{
	if (l == r)tr[u] = { l,l,w[l],0,1 };
	else
	{
		tr[u] = { l,r,0,0,1 };
		int mid = l + r >> 1;
		build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
		pushup(u);
	}
}
void modify(int u, int l, int r, int add, int mul)
{
	if (tr[u].l >= l && tr[u].r <= r)change(tr[u], add, mul);
	//树中结点,打上懒标记,查询时会更新
	else//继续往下走
	{
		pushdown(u);
		int mid = tr[u].l + tr[u].r >> 1;
		if (l <= mid)modify(u << 1, l, r, add, mul);
		if (r > mid)modify(u << 1 | 1, l, r, add, mul);
		pushup(u);
	}
}
ll query(int u, int l, int r)
{
	if (tr[u].l >= l && tr[u].r <= r)return tr[u].sum;
	pushdown(u);//往下走时通过懒标记,更新子节点
	int mid = tr[u].l + tr[u].r >> 1;
	ll sum = 0;
	if (l <= mid)sum = sum+query(u << 1, l, r)%p;
	if (r > mid)sum += query(u << 1 | 1, l, r);
	return sum%p;
}
int main()
{
	scanf("%d%d", &n, &p);
	f(int i=1, i<=n, i++)scanf("%d", &w[i]);
	build(1, 1, n);
	scanf("%d", &m);
	int a, b,c,d;
	while (m--)
	{
		scanf("%d%d%d", &a, &b, &c);
		if (a == 1)
		{
			scanf("%d", &d);
			modify(1, b, c, 0, d);
		}
		else if (a == 2)
		{
			scanf("%d", &d);
			modify(1, b, c, d, 1);
		}
		else printf("%lld\n", query(1, b, c));
	}
	return 0;
}

一文讲透3GPP TS 36.104发射链路相关指标 3GPP是制定移动通信标准的国际组织,其36.104规范对LTE基站射频性能提出详细要求。规范主要涵盖七方面:1)基站输出功率分类标准;2)LTE信道带宽计算方法;3)TD-LTE特有的发射机关断功率及时延要求;4)信号质量指标(频率误差和EVM)及其影响因素;5)邻道泄漏比(ACLR)的45dBc标准;6)带内频谱辐射模板(OBUE)的噪声限制;7)9kHz-12.75GHz频段的带外杂散辐射要求。这些技术要求保障了LTE基站的射频性能和网络质量。 阅读详情

相关推荐

Unity技能系统核心:从理论到实践构建可扩展的Buff管理器

本文深入探讨了Unity游戏开发中构建可扩展技能系统的核心——Buff管理器。通过剖析三层架构(数据、逻辑、渲染)与独立组件管理模式,提供了从Buff数据定义、管理器核心实现到可视化编辑器搭建的完整实践方案,旨在解决技能系统维护难题,提升开发效率与代码质量。

weixin_29002209的博客 255

树状数组与线段树

树状数组与线段树

qq_46118239的博客 1422

UVM自学笔记3——项目之三——从零开始搭建ahb_sramc验证平台

创建如下所示的目录结构: 2.在tb的文件下:顶层的testbench如下:在tb中,第24行的代码将uvm宏相关的内容include进来,第26行的代码把uvm_pkg包导入进来,然后将agent相关的包(ahb_pkg/sram_pkg),testcase相关的包(uvm_ahb_sram_test_pkg)以及env相关的包(uvm_ahb_sram_env_pkg)都导入进来,在第38行和39行将接口以virtual interface的形式配置给ahb_agent和sramc_agent下

ZhangShuHuai11的博客 9479

线段树和树状数组

一、线段树和树状数组的区别 线段树可以在O(log(N))时间复杂度内寻找区间极值和区间和,线段树的创建时间复杂度为O(log(N)),空间复杂度为O(>=2n-1);树状数组可以在O(log(N))的时间复杂度内计算区间极值和区间和,树状数组的创建时间复杂度为O(Nlog(N)),空间复杂度为O(N)。线段树求解的区间是任意的,越界也无所谓,但是树状数组求解的区间必须是从1开始的合法区间。 二、线段树   线段树是一种二叉搜索树,什么叫做二叉搜索树,首先满足二叉树,每个结点度小于等于二,即每个

t_zht的博客 6250

用LightRAG+Ollama搭建本地知识库:从文档上传到智能问答的完整流程

本文详细介绍了如何利用LightRAG与Ollama构建企业级本地知识库,涵盖从文档预处理到智能问答的完整流程。通过实战案例展示,该方案能高效处理海量文档,实现语义搜索与知识图谱关联分析,显著提升信息检索效率。特别适合金融、医疗等领域的技术团队快速搭建智能知识管理系统。

weixin_42535461的博客 215

不用买显示器!用USBMMID虚拟显示器软件在Win10上扩展4个屏幕(保姆级教程)

本文提供了一份详细的保姆级教程,指导用户如何在Windows 10系统上,无需购买额外硬件,通过USBMMID虚拟显示器软件扩展出多达4个虚拟屏幕。文章深入解析了虚拟显示器的核心原理,并基于微软IDD框架,手把手教学从软件获取、环境检查到安装配置的全过程,帮助开发者和多任务用户构建高效、免费的虚拟多屏工作站,实现生产力的飞跃。

backprop5master的博客 661

acwing——线段树

主要操作: 1.pushup:由子节点算父节点 ; pushdown 2.build:将一段区间初始化成一棵线段树 3.modify:修改某一个点或一个区间(需要用到懒标记) 4.query:查询某一段区间的信息 线段树是一棵满二叉树,用一维数组存。 如果一个点的编号是x:父节点x>>1,左子节点x<<1,右子节点x<<1|1 因为倒数第二层约有n个点,因此除最...

Stephen_Zhao0的博客 782

国腾|GM8775C替换TC358775/MIPI转双通道LVDS

1 产品概述 GM8775C 型DSI 转双通道 LVDS 发送器产品主要实现将MIPI DSI 转单/双通道 LVDS 功能,MIPI 支持 1/2/3/4 通道可选,最大支持 4Gbps 速率。LVDS时钟频率最高154MHz,最大支持视频格式为 FULL HD(1920 x 1200)。 该芯片主要应用于手持设备、双屏显示,大屏幕显示等应用需求。 2 产品特征 a) I/0电源电压:1.8V /3.3V; b) core 电源电压...

Vasse的博客 1073

【数据结构】树状数组和线段树

【数据结构】树状数组和线段树

Tikitian的博客 563

电子工程师必看:2024年IPC-A-610J和J-STD-001J新标准到底更新了啥?

本文深入解析2024年IPC-A-610J和J-STD-001J新标准的核心更新,重点关注电子制造领域的验收标准与工艺参数变化。新版标准强化了高密度组装和微焊接工艺的规范,包括焊料润湿角调整、BGA空洞率严格化等关键变更,为电子工程师提供实用指南。同时关联IPC-7711/21D和IPC-2221C的配套更新,助力企业快速适应新标准要求。

weixin_33694620的博客 795

机器人控制入门

机器人控制入门 对于工科领域来说,脱离实践的学习都是肤浅的,对于控制这种强调经验的技术更是如此。如果去问一个程序员怎么学习一块技术,他必然让你去多编程。机器人领域也是。如果想把基本功打扎实,那么实践更是必不可少了。 机器人控制有三个元素:控制器(算法),执行器(电机),传感器; 控制的本质就是将规划系统的指令作为输入信息,将传感器探测得到的状态信息和导航系统的定位

彼岸花 1万+

C++ 线段树,树状数组

ps:本文需要读者对递归和回溯,位运算的补码,位与操作以及前缀和有一定了解。后面在写到的时候会做简单介绍。 结构介绍: 树状数组是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值。 线段树是一个二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(lo

weixin_43899008的博客 3225

Agisoft Metashape 基于RTK技术的无人机航测免像控技术流程

利用无人机进行航空摄影测量,其使用方便、快速获得成果,已经是测绘行业外业生产的主要方式。RTK定位系统的出现,其厘米级的定位系统,使得航测无人机在作业时,能够准确获取拍照时的GPS位置,能够大量减少像控点,即使不使用像控点也能满足规范对精度的要求。可以有效解决特殊地形(如山川、峡谷、河流、沼泽、水库等)外业人员很难布设像控点的情况。

penglu366366的博客 2189

树状数组和线段树

树状数组 树状数组的优点是能在O(logn)的时间复杂度之内解决两类问题: 1.在数组的某个位置上的数加上一个数(单点修改) 2.快速求前缀和(区间查询) 如果用一般的算法,在修改某个数之后,再求前缀和,那么包含这个数的前n个数的前缀和都要进行改变,时间复杂度就从O(1)变成了O(n)。 再者如果同时进行单点修改(时间复杂度为O(n))和区间查询(时间复杂度为O(1)),平均下来就是O(n)/2,即O(n),而用树状数组来做因为时间复杂度都是O(logn),所以平均下来就是O(logn)。 树状数组也是一个

gutianyu12138的博客 1233

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

树状数组+线段树

树状数组+线段树 树状数组 动态求连续区间和 数星星 线段树 动态求连续区间和 数列区间最大值

小羊的博客 1374

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

树状数组与线段树入门

区间问题:树状数组与线段树入门

Maximum_Mighty_X的博客 691

贝叶斯统计 [韦来生编]

贝叶斯(Bayes)统计是近几十年来迅速发展起来的数理统计的一个重要分支。贝叶斯方法与经典统计方法的主要不同之处是进行统计推断时除了利用样本信息外,还要利用参数的先验信息,因此可以提高统计推断或统计决策的效果。它在经济、金融、生物、医学、自然科学和社会科学等许多领域具有广泛的应用。贝叶斯方法的研究已渗透到了统计学的几乎所有领域。作者在给中国科学技术大学概率论与数理统计专业本科生开设的“数理统计”课程中有一章专门讲授“贝叶斯方法和统计决策理论”,近十年来也给中国科学技术大学概率统计专业研究生开设过几次“贝叶斯分析”课。本书是在对过去讲稿的内容作了适当的增减和调整的基础上完成的。  本书共分六章。第1章是绪论,介绍了贝叶斯统计的若干基本概念,同时对必要的数理统计的基础知识有重点地作了回顾。第二章介绍了确定先验分布的若干可供选择的方法。第三章介绍了几类常见统计模型参数的后验分布的主要结果和计算方法。第四章和第五章分别介绍了“贝叶斯统计推断”和“贝叶斯统计决策”的内容。第六章介绍了贝叶斯统计计算的若干方法,包括蒙特卡洛方法和马尔可夫链蒙特卡洛(MCMC)方法以及相关统计软件的简介。本书的每一章附右大量的例题和习题,书末附有常用的几个表格和部分习题解答供读者查用。  大约可在54学时内讲授本书的第1章至第六章的主要内容,教师可根据学时需要选讲本书第1章至第六章的部分内容。书中标“*”号的章节可略去不讲,留给读者作为阅读材料。第六章例题中的R代码和部分例题的数据文件等可从作者个人主页下载。  作者在编写本书的过程中参考了J.O.Berger教授的《统计决策论及贝叶斯分析》和茆诗松教授编写的《贝叶斯统计》等书中的一些内容,以及《贝叶斯分析》一书中由张伟平博士编写的有关贝叶斯统计计算的一些内容。在此表示衷心感谢。  本书编写准备过程中,中国科学技术大学统计与金融系的几个研究生帮助完成了本书部分中文的录入和排版,作者对他们的辛勤工作表示真诚的感谢。高等教育出版社为本书的出版给予了大力支持。在此一并致谢。

线段树与树状数组

线段树和树状数组都是一种基于区间的修改查询操作。 这两种修改和查询操作相互影响,不能独立编写。 单值处理,区间查询 结构体完成 struct node { int l,r; ll sum; }; struct SegmentTree //单点修改,区间查询 { node tr[maxn<<2]; inline void buildInit(in...

qq_38890926的博客 311

线段树与树状数组的区别

作者:闵梓轩 链接:https://www.zhihu.com/question/54746883/answer/151823936 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 假设数组长度为n。 线段树和树状数组的基本功能都是在某一满足结合律的操作(比如加法,乘法,最大值,最小值)下,O(logn)的时间复杂度内修改单个元素并且维护区间信息

honghuidan的博客 7995

线段树 以及树状数组

树状数组 O(logn) n/2+n/4+…+1 =2n 这是从1-n-1层的节点数 假设最后一层节点数 是2n 那么一共的节点数 是4n 主要解决的问题: 单点修改 区间查询 前缀和O(1)只能解决区间查询问题 并不能为我们解决单点修改问题      前缀和 用树状数组 单点修改   O(...

weixin_45049026的博客 279

图说线段树和树状数组

图说线段树和树状数组

sunny606的专栏 5134
上一篇: 并查集-
下一篇: 前端HTML学习笔记
01star️
博客等级 码龄3年 96粉丝 63原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值