树状数组应用
树状数组应用
该栏目不断更新,各种树状数组题目
事情的起因是一道题目树上逆序对
看到题目的时候,就想到用树状数组去写,但是关于逆序对怎么用树状数组去写,记忆有些模糊了,就去翻了翻洛谷的板子题逆序对,然后惊奇的发现,原来写的方法,因为数据更新而被卡掉了。原来是用map做的离散化,因此时间复杂度是,就直接卡掉了。
因此,换了方法,想先将所有数字离散化好,再按照坐标顺序插入值(即在对应值处++),到第i个时候,逆序对为i-比a[i]小的数字个数。本来到这里就结束了。
但是再转身写树上逆序对的时候,不行。
我最开始的想法是
错误思路
枚举每一个点i,然后枚举分别从1-n-1叉树定义下的子节点区间(题目中也给了)。但是此时问题出现了,当我按坐标顺序插入值的时候,我想要知道的却是i后边的区间内,比a[i]小的节点个数。这不就尴尬了嘛,我还没到后面呢,怎么知道呢?
感觉要不行了,但是灵光乍现,突然想到之前写过的一道离线处理的一道树状数组题目数数,下面来说说延伸得到的思路。
正解思路
数数这道题目的思路就是,按照Hi值从小到大的顺序插入坐标(即在对应坐标处++),此时想要知道区间内,满足小于等于Hi条件的点,直接query(r) - query(l-1)即可,由此得到灵感。我们也可以来解决这道题。
我们可以预先将所有点按照点权的从小到大的顺序插入坐标,每次我们插入一个点i的时候,我们可以枚举对应的1 -> n-1叉树中,I的子节点区间,此时直接减一下就可以得到在k叉树中,该节点能提供的逆序对数量。
到这里,我们可以发现并总结树状数组操作的两种方式
我们再次强调一下。
我们想到用树状数组的关键词,对坐标和值同时进行限制,要立马想到树状数组
两种方式
按照值进行插入坐标
即按照下标建立树状数组
当对值与坐标均进行限制,对值的限制并不复杂
而以值建立时,需要离散化,那我们就用这种方式
否则,若可以直接以值建立会简单一些
例如,i
前,比小的数的个数,就是经典应用。
这里有一个细节问题,是否去重。
或者说,我们判断的值是否是严格大于或小于。
我们还拿刚刚那个举例子。
i
前,小于的数的个数。
i
前,小于等于的数的个数。
那如何解决呢?注意:只是以上面两个问题举例,其他相似情况自行推导一下
我们发现,只需在排序的时候,对第一个问题。
我们只需要在排序时,对相等的数,按照编号从大到小排序。这样再算的时候,直接sum(n)-sum(i)
即可。
而对于第二个问题
我们只需要在排序时,对相等的数,按照编号从小到大排序。这样再算的时候,直接sum(n)-sum(i)
即可。
逆序对
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e5 + 10;
pair<int,int> a[N];
int tr[N];
LL ans;
int n;
void add(int x,int c)
{
while(x<=n)
{
tr[x] += c;
x += x & -x;
}
}
int sum(int x)
{
int res = 0;
while(x)
{
res += tr[x];
x -= x & -x;
}
return res;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i].first),a[i].second = i;
sort(a+1,a+n+1,greater<pair<int,int>>());
for(int i=1;i<=n;i++)
{
add(a[i].second,1);
ans+=sum(a[i].second-1);
}
printf("%lld",ans);
return 0;
}
数数
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
typedef pair<int,PII> PIII;
const int N = 1e5 + 10;
int tr[N];
int n,m;
struct Node
{
int id,l,r,num;
bool operator<(const Node &w) const
{
return num < w.num;
}
};
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()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
vector<PII> a(n);
vector<Node> b(m);
vector<int> ans(m+1);
for(int i=0;i<n;i++)
{
scanf("%d",&a[i].first);
a[i].second = i + 1;
}
for(int i=0;i<m;i++)
{
int l,r,v;scanf("%d%d%d",&l,&r,&v);
b[i] = {i+1,l,r,v};
}
sort(a.begin(),a.end());
sort(b.begin(),b.end());
int k = 0;
for(int i=0;i<m;i++)
{
while(k<n&&a[k].first<=b[i].num)
{
add(a[k].second,1);
k++;
}
ans[b[i].id]=sum(b[i].r)-sum(b[i].l-1);
}
for(int i=1;i<=m;i++)
cout<<ans[i]<<" ";
cout<<"\n";
for(int i=1;i<=n;i++) tr[i] = 0;
}
return 0;
}
树上逆序对
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
pair<int,int> a[N];
int tr[N];
LL ans[N];
int n;
void add(int x,int c)
{
while(x<=n)
{
tr[x] += c;
x += x & -x;
}
}
int sum(int x)
{
int res = 0;
while(x)
{
res += tr[x];
x -= x & -x;
}
return res;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i].first),a[i].second = i;
sort(a+1,a+n+1);
for ( int i = 1;i <= n;i++ ) {
for ( int j = 1;j <= n - 1;j++ ) {
if ( j * (a[i].second - 1) + 2 > n )
break;
ans[j] += sum(min(j * a[i].second + 1, n)) - sum(j * (a[i].second - 1) + 1);
}
add(a[i].second,1);
}
for(int i=1;i<=n-1;i++)
printf("%lld ",ans[i]);
return 0;
}
HH的项链(区间去重)
历史(刷题)的经验告诉我们,处理区间去重时,我们往往会离线操作。
我们将区间按照r从小到大排序,接下来
按照顺序将所有的区间遍历到,同时不断的填充区间内的数。
此时若,该位置的值之前已经出现过,那么我们就将之前的删除,并在该位置插入该数。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
struct Node
{
int l,r;
int id;
bool operator<(Node &W)const
{
return r<W.r;
}
}Line[N];
int tr[N];
int w[N],pos[N],ans[N];
int n,m;
void add(int x,int c)
{
while(x<=n)
{
tr[x] += c;
x += x & -x;
}
}
int sum(int x)
{
int res = 0;
while(x)
{
res += tr[x];
x -= x & -x;
}
return res;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
int l,r;scanf("%d%d",&l,&r);
Line[i] = {l,r,i};
}
sort(Line+1,Line+m+1);
int idx = 1;
for(int i=1;i<=m;i++)
{
for(int j=idx;j<=Line[i].r;j++)
{
if(pos[w[j]]) add(pos[w[j]],-1);
pos[w[j]] = j;
add(j,1);
}
idx = Line[i].r + 1;
ans[Line[i].id] = sum(Line[i].r) - sum(Line[i].l-1);
}
for(int i=1;i<=m;i++) cout<<ans[i]<<endl;
return 0;
}
合适数对
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
struct Node
{
LL w,id;
bool f;
bool operator<(const Node& W) const
{
if(w==W.w) return id<W.id;
return w<W.w;
}
}a[N<<1];
int tr[N];
LL n,t;
void add(int x,int c)
{
while(x<=n)
{
tr[x] += c;
x += x & -x;
}
}
LL sum(int x)
{
LL res = 0;
while(x)
{
res += tr[x];
x -= x & -x;
}
return res;
}
int main()
{
cin>>n>>t;
LL res = 0;
LL ans = 0;
int cnt = 0;
for(int i=1;i<=n;i++)
{
LL x;cin>>x;
res += x;
if(res<t) ans++;
a[cnt].w = res - t,a[cnt++].id = i;
a[cnt].w = res,a[cnt].id = i,a[cnt++].f = 1;
}
sort(a,a+cnt);
for(int i=0;i<cnt;i++)
{
if(a[i].f) ans += sum(n) - sum(a[i].id);
else add(a[i].id,1);
}
cout<<ans<<endl;
return 0;
}
按照坐标进行插入值
其实就是按照值域建树状数组
当对值与坐标均进行限制,但是对于某一个数,我们对其值的限制有大于等于两个时。
可以直接看看我的题解。
这里面,我们对值的限制就很奇怪,因此,我们只能以值域建立树状数组。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步