1.树状数组的基本使用是求求前缀和。插入中的所有数组每一个以k结尾的sum【k】中会存有1到k的前缀和,
修改需要修改他的直接父节点,然后一直修改到小于等于n的大小。
add公式:
for(int i=x;i<=n;i+=lowbit(i))tr[i]+=c;
查询例如:12 (1100)需要查询到 8和12 这两个点刚好存储啦12 之前的所有点的前缀和。
ask公式:
for(int i=x;i;i-=lowbit(i))res+=tr[i];
return res;
2.O(n)建立树状数组:
设:a[maxn]为原数组,sum[maxn]线性求解的前缀和,tr[maxn]为树状数组;
tr[i]=a[i-lowbit(i)+1~~~~~x];
也就是原来那个nlogn建树的方式;
现在可以直接用tr[i]=sum[i]-s[i-lowbit(i)];
3.楼兰图腾
这道题目需要应用树状数组最基本的查询前缀和的方法。需要把每一个数字进行转化,统计每一个数字的贡献(最多贡献为1) 以数字的大小作为下标,已经插入中的数字下标贡献值加一 即:add(a[i],1);
题目中要找
V
V
V和 ^ 这种图形有多少种情况
每一个数字不重复。
for (int i = 1; i <= n; i ++ )
{
int y = a[i];
Greater[i] = sum(n) - sum(y);
lower[i] = sum(y - 1);
add(y, 1);
}
第一遍统计每一个数字的前面有多少数字大于它 存在Greater数组中,
统计每一个数字的前面有多少数组小于它 存在lower数组中。
memset(tr, 0, sizeof tr);
清空树状数组中的所有记录。
LL res1 = 0, res2 = 0;
for (int i = n; i; i -- )
{
int y = a[i];
res1 += Greater[i] * (LL)(sum(n) - sum(y));
res2 += lower[i] * (LL)(sum(y - 1));
add(y, 1);
}
第二遍倒找跑数组,
统计每一个数字后面。。。。。。。。 直接乘以对应Greater的统计量。
。。。。。。。后面。。。。。。。。
即为左大乘右大,
左小乘右小。
4. 一个简单的整数问题
这道题目是给你一个数组,然后做两种操作:
第一类指令形如 C l r d,表示把数列中第 l∼r 个数都加 d。
第二类指令形如 Q x,表示询问数列中第 x 个数的值。
想想一下如果不卡时间完全可以用差分数组做前缀和的差值来表示当前的值的大小
那么操作将变为俩点修改,查询为单点查询
然后将前缀和数组用树状数组来表示就是这道题目的做法。
for (int i = 1; i <= n; i ++ ) add(i, a[i] - a[i - 1]);
插入为差分数组放入树状数组中。
while (m -- )
{
char op[2];
int l, r, d;
scanf("%s%d", op, &l);
if (*op == 'C')
{
scanf("%d%d", &r, &d);
add(l, d), add(r + 1, -d);
修改树状数组中两个点的值,也就是这一段区间的值都会发生改变。
}
else
{
printf("%lld\n", sum(l));
输出该点的值。
}
}
5.243. 一个简单的整数问题2
这道题目相比于上一个题目:
C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。
Q l r,表示询问数列中第 l∼r 个数的和。
这样上一道题目中的tr[i]前缀和是原数组的值,也就是原差分数组的前缀和。
现在是原数组的前缀和,差分数组的前缀和的前缀和,也就是例如:
sum[3]=b[1]*3+b[2]*2+b[3];
把维护序列的具体值,转化为维护指令的累计影响,每次操作的影响,在l处开始,然后在r+1处消除.然后就让单点修改可以维护区间修改.
现在b数组的前缀和就是∑xi=1b[i]∑i=1xb[i] 就是经过指令后a[x]增加的值,那么序列a的前缀和a[1~x]增加的值就是:
然后通过上面这个式子,我们就把原来的数组,经过差分操作去维护两个树状数组,一个维护didi,一个维护di×idi×i这样的话,我们在区间修改的过程中,就可以在两个树状数组中去查询得到前缀和,然后同理,区间修改操作就是差分数组的修改,每次只需要修改两个点,完美的将区间再次转换为单调修改。
最后化简为两个前缀和式子。
这样最后需要维护两个tr数组。这次直接看闫总代码吧,没啥好注释的。
5. 谜一样的牛
有 n 头奶牛,已知它们的身高为 1∼n 且各不相同,但不知道每头奶牛的具体身高。
现在这 n 头奶牛站成一列,已知第 i 头牛前面有 Ai 头牛比它低,求每头奶牛的身高。
解法:
倒着安排数字先安排后面的一个一个安排到第一个就能安排完成。
1.从剩余数中找出第k小的数
2.删除找到的那个数。
这样我们可以利用树状数组来维护1到n的整个区间有多少数还没有被删掉。
最后利用二分查找那一个值正好是可以代替这个位置。
时间复杂度为nlongnlongn
闫总代码
for (int i = 1; i <= n; i ++ ) tr[i] = lowbit(i);
这条语句是一开始初始化用的,它相当于:
for (int i = 1; i <= n; i ++ ) add(i,1);
也就是每一个i里面所包含的区间长度就是它的lowbit(i);这个知识点可以对比一开始上面的形象直观的图记忆一下。
终于到最后一题。这次就是卡在这道题(没想明白具体的使用,和正确性),才来刷的第二遍树状数组。
最大上升子序列和
直接写dp的话要找到之前比当前位置小的且借用来的最大才可。
那么时间复杂度会达到n(遍历)*n(寻找前面比当前值小且dp值最大的值)
用树状数组来优化一下寻找最大值就是和前缀数组一样只不过,会更快,这里更像是歪着的线段树(每一个节点的父节点不是那个u>>1,而是u+lowbit(u)),也就是时间与线段树差不多,实现的结果一样,
但是好写。
然后这道题目中应用啦树状数组max的公式,由此可以看出树状数组可以支持全部max,全部min,全部加法。分别代表区间最大值,最小值,区间和。
另外本题需要离散化,然后最后的时间复杂度,会在n的基础上多乘一个longn
然后总时间复杂度与树状数组具体实现是一致的,会多一个乘2,乘3的量级。
#include <algorithm>
#include <cstdio>
#include <queue>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
// #define x first
// #define y second
typedef pair<int, int> PII;
const int N = 100010;
int n,w[N];
ll tr[N],f[N];
vector<int> li;
int gett(int x)
{
return lower_bound(li.begin(),li.end(),x)-li.begin()+1;
}
int lowbit(int x)
{
return x&-x;
}
void add(int x,ll v)
{
for(int i=x;i<=n;i+=lowbit(i))
tr[i]=max(tr[i],v);
}
ll query(int x)
{
ll res=0;
for(int i = x;i;i-=lowbit(i))
res=max(res,tr[i]);
return res;
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&w[i]);
li.emplace_back(w[i]);
}
sort(li.begin(),li.end());
li.erase(unique(li.begin(),li.end()),li.end());
ll res=0;
for(int i=0;i<n;i++)
{
int k=gett(w[i]);
f[i]=query(k-1)+w[i];
res=max(res,f[i]);
add(k,f[i]);
}
printf("%lld\n",res);
return 0;
}
标签:前缀,树状,int,sum,笔记,li,数组 来源: https://blog.csdn.net/weixin_45753778/article/details/118407829
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。