树状数组再理解

写点之前自己不是很理解的吧

为什么树状数组的空间大小等于原数组中最大的值

void add(int x)
{
    for(int i=x;i<=N;i+=lowbit(i))
        tree[i]+=x;
}

这个之前想不明白,现在确是一定的。
在add函数中:
传入的x参数就是你写的add(a[i])。所以是从a[i]开始存的,tree[i]的i=原来的a[i]。i是空间下标,a[i]是原来的元素,所以,懂了吗?
eg:

#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 500001;
int tree[N];
int a[5]={1,2,4,8,99};
int lowbit(int x)
{
    return x&-x;
}
void add(int x)
{
    for(int i=x;i<=N;i+=lowbit(i))
        tree[i]+=x;
}
int main()
{
    for(int i=0;i<5;i++)
    {
        add(a[i]);
    }
    for(int i=0;i<=1000;i++)
        printf("%d ",tree[i]);
}

啊啊啊 啊啊奥奥
但是如果你add的不是a[i],而是一个k,那这个树状数组就有别的用途了,可以是求逆序列,也可以是求区间最值。


树状数组模板题

没有直接用的。不然太弱智了。说的就是你。

树状数组求逆序对

题目链接

逆序对就是i<j&&a[i]>a[j]
很简单,但是人们也总能抓住重点
可以不考虑那么多,直接把这个数存到这个数对应树状数组的位置上去。但是这个题目的数据是109,数组开是能开这么大的,但是树状数组的父亲节点是这个节点的2k数量级,直接GG。
所以说,不能直接的以这个数的大小作为树状数组的数量级。
这启示我们,能不能不是这样的?我不以这个数的大小作为树状数组的下标,用别的行不行?

逆序对只考虑了两者的相对大小,他本来是什么根本不需要考虑。所以,用离散化数据的方法

现在懂了,觉得离散化是个非常巧妙的一个过程。
先看过程:

数据离散化过程
//设置结构体数组,data是要输入的数据,index是下标,从1~n
struct node{
    int data;
    int index;
}b[N];

//输入数据
for(int i=1;i<=n;i++)
     { 
        scanf("%d",&b[i].data);
        b[i].index=i;
    }

//按照原来的数据大小进行排序
sort(b+1,b+1+n,cmp);
//其中的cmp:
//cmp中,可能有重复数据。如果是重复数据,就按照下标大小进行排序
/*为啥按照下标从大到小排序:因为你往树状数组里存的就是下标。
每存进去一个,就要检查比他下标小的,有没有先存进去。
有的话就按照逆序对处理。
如果先存小的,那到大的的时候不久误判相等的为逆序的了吗?
*/
bool cmp(node a,node b)
{
    if(a.data==b.data)
        return a.index>b.index;
    return a.data>b.data;
}
int a[N];
for(int i=1;i<=n;i++)
{
	a[b[i].index]=i;
}

完成了一个啥操作呢?其实就是把原数组中的数换成了相对的大小。
eg:
1 2 3 4 5
8 7 9 4 3
按照数据元素大小进行排序
5 4 2 1 3
3 4 7 8 9
然后把数据换成i
5 4 2 1 3
1 2 3 4 5
然后把上面再按顺序排好
1 2 3 4 5
4 3 5 2 1
可以比较一下4 3 5 2 1 和 8 7 9 4 3,你会发现4 3 5 2 1正是某个数在这些数里的相对大小,而这些数的下标是没有变的。
这里是按照从小大的顺序排列的,可以理解为是排名,所以数字越小代表这个数就越小。当然也能反过来,从大到小排序,这样数字越小说明越大。
求逆序对的时候可以按照从大到小排序,也可以从小到大排序。但是思路不很一样。从小到大我不会
从大到小排序时,大的数排在前面,则下标小,我只需要看当前树状数组里已经放进去的他的子节点就知道有几个比他大而且下标也小的(但是这个地方有个坑,需要刨去这个点本身,所以查找的是1~i-1有几个)。

#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 500001;
int tree[N],b[N];
    struct node{
        int data;
        int index;
    }a[N];
bool cmp(node a,node b)
{
    if(a.data==b.data)
        return a.index>b.index;
    return a.data>b.data;
}
int n;
int lowbit(int x)
{
    return x&-x;
}
void add(int x)
{
    for(int i=x;i<=n;i+=lowbit(i))
        tree[i]++;
}
int query(int x)
{
    int res=0;
    for(int i=x;i>=1;i-=lowbit(i))
        res+=tree[i];
    return res;
}
int main()
{
    cin>>n;
        for(int i=1;i<=n;i++)
        { 
            scanf("%d",&a[i].data);
           a[i].index=i;
        }
    sort(a+1,a+1+n,cmp);
    long long int res=0;    //一开始没有long long AC 不了
    for(int i=1;i<=n;i++)
        b[a[i].index]=i;
    for(int i=1;i<=n;i++)
    {
        add(b[i]);   
        res+=query(b[i]-1);//数小的,是第i大的,反而是大的。
      //  for(int j=1;j<=n;j++)
          //      cout<<tree[j]<<" ";cout<<endl;
    }
    cout<<res;
}

题目传送门

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
两种方法,rmq和树状数组。
树状数组比较难想。感谢王佬的鼎立相助和不厌其烦。
1<<i 表示2的i次方。

//rmq
//用RMQ做求区间最值
#include<iostream>
#include<algorithm>
#include<cstring>
#include<math.h>
using namespace std;
const int N = 500001;
int a[N];//存放原始数据
int dp1[N][17];//维护区间最小值
int dp2[N][17];//维护区间最大值
int res[N];
int n;
void Init()
{
    for(int i=1;i<=n;i++)
        dp1[i][0]=dp2[i][0]=a[i];//dp[i][0]就表示这个数本身
    for(int j=1;(1<<j)<=n;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
        {
             dp1[i][j]=min(dp1[i][j-1],dp1[i+(1<<(j-1))][j-1]);
             dp2[i][j]=max(dp2[i][j-1],dp2[i+(1<<(j-1))][j-1]);
        }
}
int query_min(int l,int r)
{
    int k=log2(r-l+1);
    //cout<<"最小:"<<min(dp1[l][k],dp1[r-(1<<k)+1][k])<<endl;
    return min(dp1[l][k],dp1[r-(1<<k)+1][k]);
}
int query_max(int l,int r)
{
    int k=log2(r-l+1);
    //cout<<"最大"<<max(dp2[l][k],dp2[r-(1<<k)+1][k])<<endl;
    return max(dp2[l][k],dp2[r-(1<<k)+1][k]);
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    int sum=0;
    Init();
    for(int i=1;i<=n;i++)
    {
    	if(i==1)
    	{
    		if(query_min(2,n)>a[i]) res[++sum]=a[i];
		}
		else if(i==n)
		{
			if(query_max(1,n-1)<a[i]) res[++sum]=a[i];
		}
        else
        {
        	if(query_min(i+1,n)>a[i]&&query_max(1,i-1)<a[i])
            res[++sum]=a[i];
		}
    }
    cout<<sum<<endl;
    if(!sum) cout<<endl;
    else{
        for(int i=1;i<=sum;i++) cout<<res[i]<<" ";
    }
}
//树状数组
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 100001;
int a[N],tree[N],res[N];
int n;
struct node{
    int data;
    int index;
}b[N];
bool cmp(node a,node b)
{
    if(a.data==b.data)
        return a.index<b.index;
    return a.data<b.data;
}
int lowbit(int x)
{
    return x&-x;
}
void add(int x)
{
    for(int i=x;i<=n;i+=lowbit(i))
        tree[i]++;
}
int query(int x)
{
    int res=0;
    for(int i=x;i>=1;i-=lowbit(i))
        res+=tree[i];
    return res;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&b[i].data);
        b[i].index=i;
    }
    sort(b+1,b+1+n,cmp);
    for(int i=1;i<=n;i++)
        a[b[i].index]=i;
    int sum=0;
    for(int i=1;i<=n;i++)
    {
        add(a[i]);
       // cout<<i<<" "<<a[i]<<" "<<query(a[i])<<endl;
        //for(int j=1;j<=n;j++)
         //   cout<<tree[j]<<" ";cout<<endl;
        if(i==a[i]&&i==query(a[i]))
            {
                res[++sum]=b[i].data;
            }
    }
    cout<<sum<<endl;
    if(!sum) cout<<endl;
    else
    {
        for(int i=1;i<=sum;i++)
            printf("%d ",res[i]);
    }
}

人工湖题目加解析

a[i]=1表示他和后面的城市连着。
主要应用区间求和。

//用RMQ做求区间最值
#include<iostream>
#include<algorithm>
#include<cstring>
#include<math.h>
using namespace std;
const int N = 500001;
int n;
int tree[N];
int a[N];

int lowbit(int x)
{
    return x&-x;
}
void add(int x,int k)
{
    for(int i=x;i<=n;i+=lowbit(i))
        tree[i]+=k;
}
int query(int x)
{
    int res=0;
    for(int i=x;i>=1;i-=lowbit(i))
        res+=tree[i];
    return res;
}
int range_query(int l,int r)
{
	if(l==r)return 0;
    return query(r-1)-query(l-1);
}
void Init()
{
    for(int i=1;i<=n;i++)
    {
        a[i]=1;
        add(i,1);
    }
}
int main()
{
    int q;
    int mod,l,r,x,y;
    cin>>n>>q;
    Init(); 
    while(q--)
    {
        scanf("%d%d%d",&mod,&x,&y);
        l=min(x,y);r=max(x,y);
        if(mod==1)
        {
        	
            if(range_query(l,r)==r-l)
            	printf("Yes\n");
            	
            else if(range_query(1,l)+range_query(r,n)+a[n]==n+l-r)
            {	//cout<<l<<" "<<r<<" "<<range_query(1,l)<<" "<<range_query(r,n)<<" "<<a[n]<<endl;//a[n]
            	printf("Yes\n");
            }
            else printf("No\n");
        }
        if(mod==0)
        {
            if(l==0&&r==n)
            {
                if(a[n]==1)
            	{
            		 add(n,-1);
            		 a[n]=0; 
				}
                else{
                    add(n,1);
                    a[n]=1;
                }
            }
           else if(a[l]==1)
            {
                add(l,-1);
                a[l]=0;
            }
            else{
                add(l,1);
                a[l]=1;
            }
        }
    }

}

/*
5 100
1 2 5
0 4 5
1 4 5
0 2 3
1 3 4
1 1 3
0 1 2
0 2 3
1 2 4
1 2 5 

*/

RMQ经典:

题目传送门

//用RMQ做求区间最值
#include<iostream>
#include<algorithm>
#include<cstring>
#include<math.h>
using namespace std;
const int N = 50001;
int n;
int a[N];
int dp1[N][20],dp2[N][20];
void Init()
{
    for(int i=1;i<=n;i++)
    {
        dp1[i][0]=dp2[i][0]=a[i];
    }
    for(int j=1;(1<<j)<=n;j++)
    {
        for(int i=1;i+(1<<j)-1<=n;i++)
        {
            dp1[i][j] = min(dp1[i][j-1],dp1[i+(1<<(j-1))][j-1]);
            dp2[i][j] = max(dp2[i][j-1],dp2[i+(1<<(j-1))][j-1]);
        }
    }
}
int query(int l,int r)
{
    int k=log2(r-l+1);
    int a=max(dp2[l][k],dp2[r-(1<<k)+1][k]);
    int b=min(dp1[l][k],dp1[r-(1<<k)+1][k]);
    return a-b;
}
int main()
{
    int q;
    cin>>n>>q;
    for(int i=1;i<=n;i++)   
        scanf("%d",&a[i]);
    Init();
    while(q--)
    {
        //cout<<"n="<<n<<endl;
        int l,r;scanf("%d %d",&l,&r);
        printf("%d\n",query(l,r));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值