具体定义和性质见蓝皮书P202页(有时间再添过来)
印象笔记可能更好看一点:https://app.yinxiang.com/fx/c...
树状数组的应用
树状数组与逆序对
当然归并排序也可以求逆序对
树状数组求逆序对时间复杂度为O((N+M)logM)----M为数据范围
归并排序求逆序对的时间复杂度为O(Nlogn)
PS:
归并排序就是每次把序列二分,递归对左右两半排序,然后合并两个有序序列
归并排序求逆序对就是递归对左右两半排序时,可以把左右两半各自内部的逆序对数作为子问题计算,因此我们只需要在合并时考虑"左边一半里一个较大的数“与“右边一半里一个较小的数”构成逆序对的情形,求出这种情形的个数。合并两个有序序列A[l~mid]与A[mid+1~r]可以采用两个指针i和j分别对二者进行扫描的方式,不断比较两个指针所指向的数值a[i]和a[j]的大小,将小的那个加入到排序的结果数组中。若小的是a[j],则a[i~mid]都比a[j]要大,他们都会与a[j]构成逆序对,顺便统计到答案中。
如果数据范围较大,即M较大,可以对序列进行离散化处理,不过离散化需要排序,不如直接用归并排序求。
题目链接:https://www.acwing.com/proble...
分析:
以找'V'标志为例:
AC代码:
#include <algorithm>
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
const int N = 200010;
int n;
int a[N];
long long int w1[N],w2[N],w3[N],w4[N];
long long int c[N];
long long int ask(int x)//查询操作
{
long long int ans = 0;
for(int i=x;i;i-=(i&-i))
{
ans += c[i];
}
return ans;
}
void add(int x)//添加操作
{
for(;x<=n;x+=(x&-x))
{
c[x]+=1;
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++)
{
w1[i]=ask(n)-ask(a[i]);//利用前缀和思想求出区间[1,i-1]中[a[i]+1,n]范围内的数
w3[i]=ask(a[i]-1);//利用前缀和思想求出区间[1,i-1]中[1,a[i]-1]范围内的数
add(a[i]);
}
memset(c,0,sizeof(c));
for(int i=n;i>=1;i--)
{
w2[i]=ask(n)-ask(a[i]);
w4[i]=ask(a[i]-1);
add(a[i]);
}
long long int ans1 = 0,ans2=0;
for(int i=2;i<=n-1;i++)
{
ans1+=w1[i]*w2[i];
ans2+=w3[i]*w4[i];
}
printf("%lld %lld\n",ans1,ans2);
return 0;
}
下一个问题:树状数组的扩展应用,题目链接:https://www.acwing.com/proble...
树状数组仅支持单点增加和区间查询
但本题问的是单点查询和区间增加,所以我们就要转化一下。
考虑差分的思想,新建一个数组b[],初始全部为0,对于每条“C l r d”指令,b[l]加上d,b[r+1]减去d(如果不明白差分,可以看这个https://app.yinxiang.com/fx/0...),这样区间查询时就可以变成单点查询。
举个例子
初始状态:
#include <algorithm>
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
const int N = 100010;
int a[N], b[N];
int n,m;
char ch;
int l,r,d;
int ask(int x)
{
int ans = 0;
for(;x;x-=(x&-x))
{
ans+=b[x];
}
return ans;
}
void add(int x,int d)
{
for(;x<=n;x+=(x&-x))
{
b[x]+=d;
}
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
while (m--)
{
getchar();
scanf("%c",&ch);
if(ch=='Q')
{
scanf("%d",&l);
printf("%d\n",a[l]+ask(l));
}
else
{
scanf("%d %d %d",&l,&r,&d);
add(l,d);
add(r+1,-d);
}
}
return 0;
}
现在问题升级,不是单点询问了,变为了区间询问和区间修改
依旧是采用差分的思想,区间修改是与上一个题亦同,当区间询问时,即执行语句“Q l r”时,
我们要计算的是a[1,r]减去a[1,l-1],但由于我们在修改时是对差分数组b[]进行操作,所以询问时,我们仍然要加上b[]
以计算a[1,r]为例:
i=1 b1 a[1]要加的值
i=2 b1 b2 a[2]要加的值
i=3 b1 b2 b3 a[3]要加的值
...... ......
i=r b1 b2 b3 .... br a[r]要加的值
补全矩阵:
i=0 b1 b2 b3 .... br
i=1 b1 b2 b3 .... br
i=2 b1 b2b3 .... br
i=3 b1 b2 b3 .... br
......
i=r b1 b2 b3 .... br
矩阵和为(r+1)*(b1+b2+.....+br)
再减去补充的部分,即得到之前矩阵的和(r+1)*(b1+b2+.....+br)-(b1+2b2+3b3+....+rbr)
要有两个树状数组tr1[]维护b[],tr2[]维护i*b[]
#include <algorithm>
#include <iostream>
#include <stdio.h>
#include <string.h>
#define lowbit(x) x&-x
using namespace std;
const int N = 100010;
int a[N];
long long int sum[N];
long long int tr[2][N];
int n,m;
char ch[2];
int l,r,d;
long long int ask(int k,int x)
{
long long int ans = 0;
for(;x;x-=(x&-x))
{
ans+=tr[k][x];
}
return ans;
}
void add(int k,int x,long long int d)
{
for(;x<=n;x+=(x&-x))
{
tr[k][x]+=d;
}
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i] = sum[i-1]+a[i];
}
while (m--)
{
getchar();
scanf("%s",ch);
if(ch[0]=='Q')
{
scanf("%d %d",&l,&r);
long long int ret = sum[r]-sum[l-1];
ret+=(r+1)*ask(0,r)-l*ask(0,l-1);
ret-=ask(1,r)-ask(1,l-1);
printf("%lld\n",ret);
}
else
{
scanf("%d %d %d",&l,&r,&d);
add(0,l,d);
add(0,r+1,-d);
add(1,l,l*d);
add(1,r+1,-(r+1)*d);
}
}
return 0;
}
下一题链接:https://www.acwing.com/proble...
分析:
后面不再枚举......
由上分析可知,我们要在1-n范围内建立树状数组,然后从后往前找第a[i]+1小的数,找完之后再在树状数组中删掉这个数。
在树状数组中删掉一个数很简单,只需要执行单点增加操作,增加-1即为删掉这个数。
现在最麻烦的就是怎么找第k小的数,我们可以利用二分查找,设这个数为x,如果x是第k小的,那么区间[1,x]上的数一定为k。
代码:
#include <algorithm>
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <vector>
#define lowbit(x) x & -x
using namespace std;
const int N = 100010;
int n;
int t[N];
void add(int x,int d)
{
for(;x<=n;x+=lowbit(x))
{
t[x]+=d;
}
}
bool ask(int x,int k)
{
int ans = 0;
for(;x;x-=lowbit(x))
{
ans += t[x];
}
if(ans>=k)
{
return true;
}
return false;
}
int b[N],a[N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
add(i,1);//每个数加1个
}
for(int i=2;i<=n;i++)
{
scanf("%d",&b[i]);
}
for(int i=n;i>=1;i--)
{
int l=0,r=n;
int k = b[i]+1;
while (l<r)
{
int mid = (l+r)>>1;
if(ask(mid,k))
{
r=mid;
}
else
{
l=mid+1;
}
}
a[i]=l;
add(l,-1);
}
for(int i=1;i<=n;i++)
{
cout<<a[i]<<endl;
}
return 0;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。