树状数组

树状数组适用范围:单点修改,区间查询、区间修改,单点查询、区间修改,区间查询。

对不变的数组进行区间查询,前缀和无疑是不错的选择;然而对于需要动态修改的数组,又有该效率查询需求的,可以使用树状数组优化(对数阶的查询与修改)。

·单点修改,区间查询:

#include<bits/stdc++.h> 
using namespace std;
#define int long long
#define endl '\n'
const int N=5e5+10;
int tree[N],n;//树状数组、原始数组长度 
int lowbit(int x)//计算tree[x]管辖长度 
{
	return x&-x;
}
void add(int x,int k)//给原数组第x位上加k 
{
	while(x<=n)
	{
		tree[x]+=k;
		x+=lowbit(x);
	}
}
int sum(int x)//求原数组1~x元素的和,相当前缀和 
{
	int ans=0;
	while(x>0)
	{
		ans+=tree[x];
		x-=lowbit(x);
	}
	return ans;
}
signed  main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int op,x,y,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		cin>>y;
		add(i,y);
	}
	while(k--)//单点修改,区间查询 
	{
		cin>>op>>x>>y;
		if(op==1){
			add(x,y);
		}else{
			cout<<sum(y)-sum(x-1)<<endl;
		}
	}
}

·区间修改,区间查询:

区间的高效的率修改,必然要用到差分;高效查询区间,必然要用到前缀和。

为了高效区间修改与查询,须先了解前缀和与查分的关系:

求前缀要用到d[i]、d[i]*i的前缀和,二者均可以用树状数组进行维护。这样区间查询就不成问题了。

#include<bits/stdc++.h>
using namespace std;
#define int long long 
#define endl '\n'
//区间修改,区间查询
//记s[i]为a[i]的前缀和
//用两个数组维护a[n]的差分数组d[i],d[i]*i,其前缀和分别记作sum1[i],sum2[i],则s[i]=(i+1)*sum1(i)-sum2(i)
//sum1,sum2用一个函数sum实现即可
const int N=1e6+10;
int n,a[N],d[N],tr1[N],tr2[N],m;

void add(int x,int k)
{
    int val=x*k;
    while(x<=n)
    {
        tr1[x]+=k;
        tr2[x]+=val;//不可写成x*k,在迭代的过程中,x的值会发生变化!
        x+=x&-x;
    }
}

int sum(int *t,int x)
{
    int ans=0;
    while(x>0){
        ans+=t[x];
        x-=x&-x;
    }
    return ans;
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++){
    d[i]=a[i]-a[i-1];
    add(i,d[i]);
     }
    int op; 
    while(m--)
    {
        cin>>op; int L,R,v;
        if(op==1)//区间修改
        {
            cin>>L>>R>>v;
            add(L,v); add(R+1,-v);
        }
        else//区间查询
        {
            cin>>L>>R;
            cout<<sum(tr1,R)*(R+1)-sum(tr2,R)-(sum(tr1,L-1)*L-sum(tr2,L-1))<<endl;//sum[R]-sum[L-1];
        }
    }
}


对于区间修改,单点查询,在此基础上直接求查分数组的前缀和即可

·应用:

·求逆序数:

牛客·逆序数

将数组里的元素逐一插入哈希表,哈希表中h[i]位置上的前缀和,即小于i的数量,h[n]-h[a[i]即大于a[i]的元素数量。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10;
int tree[N],n;
void add(int x)
{
	while(x<=n)
	{
		tree[x]++;
		x+=x&-x;
	}
}
int sum(int x)
{
	int ans=0;
	while(x)
	{
		ans+=tree[x];
		x-=x&-x;
	}
	return ans;
}

signed  main()
{
	cin>>n;
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		int x; cin>>x;
		ans+=sum(n)-sum(x);
		add(x);
	}
	cout<<ans;
	return 0;
}

此处,树状数组tree维护的是哈希表,优化了哈希表前缀和的查询时间,tree进行的是单点修改,区间查询。复杂度为O(nlogn)。

洛谷·Above the Median G

把>=m的元素替换成1,<m的替换成-1进行等效处理,求前缀和s,任选i<j,s[j]-s[i]>=0可以判定在区间[i+1,j]上的子串是满足条件的,在j前找小于等于s[j]的即可。

#include<bits/stdc++.h> 
using namespace std;
#define int long long
#define endl "\n"
const int p=1e9+7;
const int mod=998244353;
const int N=2e5+10;
int n,m;
int t[N],a[N];
void add(int x)
{
    while(x<=n*2+1)
    {
        t[x]++;
        x+=x&-x;
    }
}
int sum(int x)
{
    int ans=0;
    while(x>0)
    {
        ans+=t[x];
        x-=x&-x;
    }
    return ans;
}

void solve()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        if(x>=m) a[i]=a[i-1]+1;
        else a[i]=a[i-1]-1;
    }
    int ans=0;
    add(0+n);
    for(int i=1;i<=n;i++)
    {
        ans+=sum(a[i]+n);
        add(a[i]+n);
    }
   cout<<ans;
}

signed main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int t;
	t=1;
//	cin>>t;
	while(t--)
	{
		solve();
	}
	return 0;	
}

同逆序数题相似,维护哈希表,注意这里的前缀和值可能为负,需要树状数组内存需开大点,将所有值+n就解决了。

离散化+二分查找+树状数组

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],b[N],t[N],n;
void add(int x)
{
    while(x<=n)
    {
        t[x]++;
        x+=x&-x;
    }
}
int sum(int x)
{
    int ans=0;
    while(x)
    {
        ans+=t[x];
        x-=x&-x;
    }
    return ans;
}
int find(int x)//二分答案
{
    int l=1,h=n,mid;
    while(l<h)
    {
        mid=l+h>>1;//第二种写法,ans满足sum(ans)>=x,ans+1有可能也满足sum(ans+1)>=x,因为数据是离散化的,而ans-1一定不满足sum(ans-1)>=x
		//故用第二种写法。
        if(sum(mid)<x) l=mid+1;
        else h=mid;
    }
    return b[l];
}
pair<int,int>p[N];
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
   cin>>n;
   for(int i=1;i<=n;i++){
    cin>>p[i].first; p[i].second=i;
   }
   //离散化
   sort(p+1,p+1+n);
   for(int i=1;i<=n;i++)
   {
       a[p[i].second]=i;
       b[i]=p[i].first;
   }

   cout<<b[a[1]]<<endl;
    add(a[1]);
   for(int i=2;i<=n;i++)
   {
       add(a[i]);
      if(i&1){
       cout<<find(i+1>>1)<<endl;
       } 
   }
   
}

24/8/10

  • 19
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值