参考资料
acwing算法提高课-数据结构-树状数组
引入
树状数组是一种支持 单点修改 和 区间求和 的,代码量小的数据结构。O(logn)
前置知识lowbit()
int lowbit(int x)
{
return x & -x;
}
// x 的二进制中,最低位的 1 以及后面所有 0 组成的数。
// lowbit(0b01011000) == 0b00001000
// ~~~~^~~~
// lowbit(0b01110010) == 0b00000010
// ~~~~~~^~
树状数组
t[x] 表示 a[x-lowbit(x)+1, x] 区间之和。
1、区间右端点为a[x]
2、区间长度为lowbit(x)
3、下标从1开始存储
区间查询
int getsum(int x) //求a[1]到a[x]之和(前缀和)
{
int sum = 0;
for(int i=x;i;i-=lowbit(i))
sum+=t[i];
return sum;
}
区间修改
int add(int x,int k) //给单点 a[x] 增加 k
{
for(int i=x;i<=n;i+=lowbit(i))
t[i]+=k;
}
模板题
1、楼兰图腾
P1637 三元上升子序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
第二题比较经典,涉及离散化,建议熟练。
//楼兰图腾
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
typedef long long LL;
int n;
int a[N], t[N]; //t[i]表示树状数组i结点覆盖的范围和
int Lower[N], Greater[N];
//Lower[i]表示左边比第i个位置小的数的个数
//Greater[i]表示左边比第i个位置大的数的个数
int lowbit(int x)
{
return x & -x;
}
void add(int x, int k)//将序列中第x个数加上k
{
for(int i = x; i <= n; i += lowbit(i))
t[i] += k;
}
int ask(int x) //查询序列前x个数的和
{
int sum = 0;
for(int i = x; i; i -= lowbit(i))
sum += t[i];
return sum;
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
//从左向右,依次统计每个位置左边比第i个数y小的数的个数、以及大的数的个数
for(int i = 1; i <= n; i++)
{
int y = a[i]; //第i个数
//在前面已加入树状数组的所有数中统计在区间[1, y - 1]的数字的出现次数
Lower[i] = ask(y - 1);
//在前面已加入树状数组的所有数中统计在区间[y + 1, n]的数字的出现次数
Greater[i] = ask(n) - ask(y);
//将y加入树状数组,即数字y出现1次
add(y, 1);
}
//清空树状数组,从右往左统计每个位置右边比第i个数y小的数的个数、以及大的数的个数
memset(t, 0, sizeof t);
LL resA = 0, resV = 0;
//从右往左统计
for(int i = n; i >= 1; i--)
{
int y = a[i];
resA += (LL)Lower[i] * ask(y - 1);
resV += (LL)Greater[i] * (ask(n) - ask(y));
//将y加入树状数组,即数字y出现1次
add(y, 1);
}
printf("%lld %lld\n", resV, resA);
return 0;
}
/*这个算法可以解决n个任意数字的情况,对于本题来说,
没必要反着再跑一次,求得位置i前面小于a[i]数字的个数x以后,
已知一共有a[i] - 1个数小于a[i]
剩下的a[i] - 1 - x个数一定就在位置i的右边了*/
//三元上升子序列
#include <bits/stdc++.h>
using namespace std;
const int N = 3e4+10;//注意如果开1e5+10,add函数中注意循环截止条件为<=N而非n
typedef long long ll;
ll n;
ll a[N],b[N], t[N];
ll lower[N];//表示第i个数左边比它小的数的个数
int lowbit(int x)
{
return x & -x;
}
void add(int x, int k)
{
for(int i = x; i <= n; i += lowbit(i)) //注意截止条件 n为t的数组大小,而非a的数组大小
t[i] += k;
}
ll ask(int x)
{
ll sum = 0;
for(int i = x; i; i -= lowbit(i))
sum += t[i];
return sum;
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n;
for(int i = 1; i <= n; i++)
{
cin>>a[i];
b[i]=a[i];
}
sort(a+1,a+1+n);
int len=unique(a+1,a+1+n)-(a+1);
for(int i=1;i<=n;i++)
{
b[i]=lower_bound(a+1,a+1+len,b[i])-a;
}
for(int i = 1; i <= n; i++)
{
lower[i] = ask(b[i] - 1);
add(b[i], 1);
}
memset(t,0,sizeof t);//注意清除
ll ans=0;
for(int i = n; i >= 1; i--)
{
ll cnt=(n-i)-ask(b[i]);//右边的个数-小于等于它的个数=大于它的个数
ans+=lower[i]*cnt;
add(b[i], 1);
}
cout<<ans;
return 0;
}
2、一个简单的整数问题
区间修改(增加一个数),单点查询
思路:求差分数组,对差分数组进行操作,转化为单点修改,区间查询(求和)
#include <bits/stdc++.h>
using namespace std;
const int N = 100009;
int n, m, a[N], t[N];
int low_bit(int x)
{
return x & -x;
}
void add(int x,int y)
{
for(;x <= n;x += low_bit(x)) t[x] += y;
return;
}
int ask(int x)
{
int ans = a[x];
for(;x;x -= low_bit(x))
ans += t[x];
return ans;
}
int main()
{
cin >> n >> m;
for(int i = 1;i <= n;++ i) cin >> a[i];
char ch;
for(int i = 1, l, r, d;i <= m;++ i)
{
cin >> ch;
if (ch == 'C')
{
cin >> l >> r >> d;
add(l, d); add(r + 1, -d);
}
if (ch == 'Q')
{
cin >> d;
cout << ask(d) << endl;
}
}
return 0;
}
3、逆序对
P1908 逆序对 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P1774 最接近神的人 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
后者考察的其实就是逆序对。
给出两种不同的离散化方式。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+10;
int n;
struct node
{
int idx,num;
};
node a[N];
ll t[N];
int mp[N];//映射
bool cmp(node x,node y)
{
if(x.num==y.num)
return x.idx<y.idx;
return x.num<y.num;
}
int lowbit(int x)
{
return x&-x;
}
void add(int x,int k)
{
for(int i=x;i<=n;i+=lowbit(i)) t[i]+=k;
}
ll sum(int x)
{
ll res=0;
for(int i=x;i;i-=lowbit(i)) res+=t[i];
return res;
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i].num;
a[i].idx=i;
}
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++)//离散化
{
mp[a[i].idx]=i;//将原来的位置映射到排序后的位置
}
ll ans=0;
for(int i=n;i>=1;i--)//从后往前,计算比当前数小的数
{
ans+=sum(mp[i]-1);
add(mp[i],1);
}
cout<<ans;
return 0;
}
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+10;
int n;
ll a[N],b[N];
ll t[N];
int lowbit(int x)
{
return x&-x;
}
void add(int x,int k)
{
for(int i=x;i<=n;i+=lowbit(i)) t[i]+=k;
}
ll sum(int x)
{
ll res=0;
for(int i=x;i;i-=lowbit(i)) res+=t[i];
return res;
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
b[i]=a[i];
}
sort(a+1,a+1+n);
int len=unique(a+1,a+1+n)-(a+1);//返回去重后的数组长度
for(int i=1;i<=n;i++)
{
b[i]=lower_bound(a+1,a+1+len,b[i])-a;//离散化
}
ll ans=0;
for(int i=n;i>=1;i--)
{
ans+=sum(b[i]-1);
add(b[i],1);
}
cout<<ans;
return 0;
}