目录
树状数组的作用
(1)树状数组的经典模板
(2)关于记忆模板
楼兰图腾
一个简单的整数问题
一个简单的整数问题2(困难!)
谜一样的牛
我不会数学证明,但我可以学,会用就行,你知道我听了y总讲了一个小时证明的痛楚吗
相较于原数组a[N],单点增加的时间复杂度为O(1),但区间查询和的时间复杂度为O(N)
相较于前缀和数组pre[N],区间查询和的时间复杂度为O(1),但单点增加的复杂度为O(N)
因此我们选择了折中的办法就是 让这两个操作都为logN的复杂度
#include
#include
using namespace std;
const int N=1e5+10;
int a[N];
int tr[N];
int n;//点数
int lowbit(int x)
{
return x & -x;
}
void add(int x,int c)//在x的位置上加上常数c
{
for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=c;
}
int sum(int x)//求x位置前的前缀和
{
int res=0;
for(int i=x;i;i-=lowbit(i)) res+=tr[i];
return res;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
while(1)
{
cout<<"第一次查询前缀和(1)"<
241. 楼兰图腾 - AcWing题库
核心思路:
#include
#include
#include
using namespace std;
const int N=2000010;
typedef long long ll;
int n;
int a[N],t[N];//t[i]表示树状数组i结点覆盖的范围和
//Lower[i]表示左边比第i个位置小的数的个数
//Greater[i]表示左边比第i个位置大的数的个数
int Lower[N],Greater[N];
//返回非负整数x在二进制表示下最低位1及其后面的0构成的数值
int lowbit(int x)
{
return x&-x;
}
//将序列中第x个数加上k
void add(int x,int k)//向右上方以lowbit(i) 为单位 在t数组中 区间加上k 维护
{
for(int i=x;i<=n;i+=lowbit(i)) t[i]+=k;
}
//查询序列前x个数的和
int ask(int x)//向左上方以lowbit(i) 为单位 查询
{
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);//注意这里的y是a[i],也就是数字的大小,因为是已经按位置进行枚举的了
//因此,不需要考虑 位置的先后顺序,而只用考虑数字的大小关系
}
//清空树状数组,从右往左统计每个位置右边比第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);//向右查询 [1,y-1]的数字的 总个数
resV+=(ll)Greater[i]*(ask(n)-ask(y));//向右查询 [y+1,n]的数字的 总个数
add(y,1);//同理
}
printf("%lld %lld\n",resV,resA);
return 0;
}
242. 一个简单的整数问题 - AcWing题库
Code:预处理(包括其本身的差分)
#include
#include
#include
using namespace std;
typedef long long ll;
const int N=100010;
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;
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
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));
//树状数组中的前缀和维护使得差分数组能更快地求出 1~i的差分数组的和
}
return 0;
}
Code:预处理:只记录变化区间的差分《算法竞赛进阶指南》
#include
#include
using namespace std;
const int N=1e5+10;
int a[N];
int tr[N];//树上差分
int n,m;
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()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
while(m--)
{
char op[2];
int l;
scanf("%s%d",op,&l);
if(*op=='C')//区间添加操作
{
int r,d;
scanf("%d%d",&r,&d);
add(l,d),add(r+1,-d);
}
else
{
printf("%d\n",sum(l)+a[l]);
}
}
return 0;
}
243. 一个简单的整数问题2 - AcWing题库
y总的字有待提高....,我简单模拟一下吧,方便看懂
例子:比如从在区间[1,x]中 求a[]的和
规定:b[]为a[]的差分数组
解法:
朴素解法:a[1]+a[2]+a[3]+...+a[x] ----(1)
那么根据差分数组的特性则有:
a[1]=b[1]
a[2]=b[1]+b[2]
a[3]=b[1]+b[2]+b[3]
a[x]=b[1]+b[2]+b[3]+...+b[x] ----(2)
那么根据(2)式的特性,可以将(1)式改写为:
(b[1])+(b[1]+b[2])+(b[1]+b[2]+b[3])+...+(b[1]+b[2]+b[3]+...+b[x]) ----(3)
不难得出规律,在[1,x]区间中:
有 x-0个 b[1]
有 x-1个 b[2]
有 x-2个 b[3]
.....
有 x-(x-1)个 b[x]
那么y总的思路是:构造下面图片的这个 矩阵
目的是:对于(3)式 而言:就算用前缀和求出了 每一段()的和,但是仍然要用一层循环将其相加
所以实际上的时间复杂度仍然为O(N),那么我们就要考虑是不是可以构造出 *两个前缀和数组来优化*
实际上:图片中蓝色区域中的 数字才是 我们要求的数值
因此根据很简单的填充法:我们可以先求出整个 矩阵的数值,然后再减去红色区域的数值
那么根据图形不难得出如下公式:
矩阵的数值:(b[1]+b[2]+b[3]+...+b[x])*(x+1) ————(4)
红色区域的数值:(1* b[1]+2* b[2]+3* b[3]+...+x*b[x]) ————(5)
观察(4) 和 (5) 式子
不难得出 已经构造出了 两个前缀和数组
(4) 中的前缀和数组是 sum(x) 用tr1维护
(5) 中的前缀和数组是 i*sum(x) 用tr2维护
~~剩下的就与一个简单整数问题一样了~~
看不清的同学:我用截图吧QAQ md格式放到这里就好丑.....
Code:
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N=100010;
int n,m;
int a[N];
ll tr1[N];//维护b[i]的前缀和
ll tr2[N];//维护b[i]*i的前缀和
int lowbit(int x)
{
return x & -x;
}
void add(ll tr[],int x,ll c)
{
for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=c;
}
ll sum(ll tr[],int x)
{
ll res=0;
for(int i=x;i;i-=lowbit(i)) res+=tr[i];
return res;
}
ll prefix_sum(int x)//数学构造:详细见图片
{
return sum(tr1,x)*(x+1)-sum(tr2,x);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++)//构造树上差分
{
int b=a[i]-a[i-1];
add(tr1,i,b);
add(tr2,i,(ll)b*i);
}
while(m--)
{
char op[2];
int l,r,d;
scanf("%s%d%d",op,&l,&r);
if(*op=='Q')
{
printf("%lld\n",prefix_sum(r)-prefix_sum(l-1));
}
else
{
scanf("%d",&d);
//a[l]+=d
add(tr1,l,d),add(tr2,l,l*d);
//a[r+1]-=d
add(tr1,r+1,-d),add(tr2,r+1,(r+1)*(-d));
}
}
return 0;
}
244. 谜一样的牛 - AcWing题库
样例解释:
样例解释
核心思路:
这道题其实和 楼兰图腾 很像
#include
#include
#include
using namespace std;
const int N=100010;
int n;
int h[N];
int ans[N];
int 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()
{
scanf("%d",&n);
for(int i=2;i<=n;i++) scanf("%d",&h[i]);
for(int i=1;i<=n;i++) add(i,1);
for(int i=n;i;i--)
{
int k=h[i]+1;
int l=1,r=n;
while(l>1;
if(sum(mid)>=k) r=mid;
else l=mid+1;
}
ans[i]=l;
add(l,-1);
}
for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
return 0;
}