首先我们都了解过树状数组这一短小精悍的利器,知道它和差分以及推公式的简单结合。但很显然问题不会那么裸。
下面我们将讨论两道关于树状数组的简单应用题,看看具体包装下的树状数组题目
一. 楼兰图腾 acwing241.
题目描述:在完成了分配任务之后,西部 314314 来到了楼兰古城的西部。
相传很久以前这片土地上(比楼兰古城还早)生活着两个部落,一个部落崇拜尖刀(V
),一个部落崇拜铁锹(∧
),他们分别用 V
和 ∧
的形状来代表各自部落的图腾。
西部 314314 在楼兰古城的下面发现了一幅巨大的壁画,壁画上被标记出了 nn 个点,经测量发现这 nn 个点的水平位置和竖直位置是两两不同的。
西部 314314 认为这幅壁画所包含的信息与这 nn 个点的相对位置有关,因此不妨设坐标分别为 (1,y1),(2,y2),…,(n,yn)(1,y1),(2,y2),…,(n,yn),其中 y1∼yny1∼yn 是 11 到 nn 的一个排列。
西部 314314 打算研究这幅壁画中包含着多少个图腾。
如果三个点 (i,yi),(j,yj),(k,yk)(i,yi),(j,yj),(k,yk) 满足 1≤i<j<k≤n1≤i<j<k≤n 且 yi>yj,yj<ykyi>yj,yj<yk,则称这三个点构成 V
图腾;
如果三个点 (i,yi),(j,yj),(k,yk)(i,yi),(j,yj),(k,yk) 满足 1≤i<j<k≤n1≤i<j<k≤n 且 yi<yj,yj>ykyi<yj,yj>yk,则称这三个点构成 ∧
图腾;
西部 314314 想知道,这 nn 个点中两个部落图腾的数目。
因此,你需要编写一个程序来求出 V
的个数和 ∧
的个数。
输入格式
第一行一个数 nn。
第二行是 nn 个数,分别代表 y1,y2,…,yny1,y2,…,yn。
输出格式
两个数,中间用空格隔开,依次为 V
的个数和 ∧
的个数。
数据范围
对于所有数据,n≤200000n≤200000,且输出答案不会超过 int64int64。
y1∼yny1∼yn 是 11 到 nn 的一个排列。
输入样例:
5
1 5 3 2 4
输出样例:
3 4
思路与思考过程:
首先是1e5,所以应该是一个线性或者logn的问题
首先本题可以抽象后就是,对于任意一个位置的数字,统计他前面有几个数字比他大,后面有几个数字比他大,根据乘法原理,可以很容易求出题目要求的个数问题,但本题的关键在于能想到用树状数组来做,首先本题和裸的树状数组不同点在于他不是统计区间和,而是统计区间中总共有几个数字出现了(是一个排列,不用考虑数字重复问题),同时是单点修改,动态的区间查询,只满足这两个基本操作就可以了从这一点出发就不难想到要使用树状数组来做。
下面是代码,思路比较不容易想出,代码就没那么难了,关键在于思路的抽象
乘法原理+树状数组
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e5+10;
int tr[N];
LL great[N],lower[N];
LL n,res1,res2;
int q[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()
{
cin>>n;
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
q[i]=x;
great[i]=sum(n)-sum(x);//x+1 -- n;
lower[i]=sum(x-1);
add(x,1);
}
memset(tr,0,sizeof tr);
for(int i=n;i>=1;i--)
{
great[i]=great[i]*(LL)(sum(n)-sum(q[i]));
lower[i]=lower[i]*(LL)(sum(q[i]-1));
add(q[i],1);
}
for(int i=1;i<=n;i++)
res1+=great[i],res2+=lower[i];
cout<<res1<<" "<<res2;
}
小结:这里其实是树状数组的一个基本应用就是离线的处理每一个数字前面比他大的数字有多少个的问题,树状数组是把这个问题转换成了区间求和和单点更新。
二. 逆序对 洛谷p1908
思路:这道题数字在1e9,个数只有5e5,所以需要先离散化预处理再跑一遍树状数组求任意一个数字左边比他大的数字有几个就ok了。
知识铺垫:自己之前离散化也没学过(太蒟蒻了),所以先分享一道关于离散化模板的题目,很多时候树状数组和线段树的题目都要用到离散化。
AcWing 802. 区间和
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0 。
现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r][l,r] 之间的所有数的和。
输入格式
第一行包含两个整数 n和 m。
接下来 n 行,每行包含两个整数 x和 c。
再接下来 m 行,每行包含两个整数 l 和 r。
输出格式
共 m 行,每行输出一个询问中所求的区间内数字和。
数据范围
−109≤x≤109−109≤x≤109,
1≤n,m≤1051≤n,m≤105,
−109≤l≤r≤109−109≤l≤r≤109,
−10000≤c≤10000−10000≤c≤10000
输入样例:
3 3
1 2
3 6
7 5
1 3
4 6
7 8
输出样例:
8
0
5
没什么花哨的玩意,就是一个离散化的基础模板,但是很多题目都是好多个模板拼接起来组成的
所以要重视基础的作用
下面是代码:
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
typedef pair<int,int>PII;
vector<int>alls;
vector<PII>add,query;
int n,m;
int a[N],s[N];
int find(int x)
{
int l=0,r=alls.size()-1;
while(l<r)
{
int mid=l+r>>1;
if(alls[mid]>=x)r=mid;
else l=mid+1;
}
return l+1;
}
int main()
{
cin>>n>>m;
while(n--)
{
int x,y;
scanf("%d%d",&x,&y);
add.push_back({x,y});
alls.push_back(x);
}
while(m--)
{
int l,r;
scanf("%d%d",&l,&r);
query.push_back({l,r});
alls.push_back(l);
alls.push_back(r);
}
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
for(auto item:add)a[find(item.first)]+=item.second;
for(int i=1;i<=alls.size();i++)s[i]=s[i-1]+a[i];
for(auto item:query)printf("%d\n",s[find(item.second)]-s[find(item.first)-1]);
}
有了离散化,这个逆序对的题目就迎刃而解了。
注意答案要用longlong来存(自己一发就错在这上面了。。。)
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
typedef long long LL;
int tr[N];
int n;
vector<int>alls;
int q[N];
int lowbit(int x)
{
return x&-x;
}
void add(int x,int c)
{
for(int i=x;i<=alls.size();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 find(int x)
{
int l=0,r=alls.size()-1;
while(l<r)
{
int mid=l+r>>1;
if(alls[mid]>=x)r=mid;
else l=mid+1;
}
return l+1;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)scanf("%d",&q[i]),alls.push_back(q[i]);
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
LL res=0;
for(int i=1;i<=n;i++)
{
int st=find(q[i]);
// printf("hehe\n");
q[i]=st;
res+=sum(alls.size())-sum(st);
add(st,1);
}
cout<<res;
}
参考资料:acwing算法基础课,算法提高课