第一篇博客就是树状数组,已经过去半年了我树状数组还是只会个模板= =
CF1042D的题解一直看不懂,看到下面有人说和逆序对有关系,所以还是先补一下逆序对吧。
洛谷P1908是逆序对的模板题,数据很强,很好,就是题解质量参差不齐,很多人在数据加强后根本都是WA的,所以果然还是自己总结一下吧……
当给定的序列只有1-n并且没有重复时,一种简单的方法也就是大白上的方法就是直接用树状数组维护,一开始将树状数组初始化成0,然后从数组第一项开始,每次add(a[i],1)。注意到query(a[i]-1)是所有在原数组中在a[i]前面并且小于a[i]的数的个数*,所以所有在a[i]后面并且小于a[i]的数就是i-query(a[i]-1)(如果下标从1开始那么就是i-1-query(a[i]-1)),也就是在树状数组中a[i]前面那些为0的部分。
*因为在树状数组中维护的是a[i]的大小,a[i]前面的数就是比a[i]小的数,而在这个时刻没有被赋值成1的必然在原数组中排在a[i]的后面,也就是逆序对了。每次计数的是以a[i]为首的逆序对,所以不会有重复。
还有另一种方法。我们把a[i]装成一个结构体Node,其中val表示a[i]的大小,num表示在数组中的位置(从1开始)。
struct Node{
int val, num;
}
bool cmp(Node a, Node b){
return a.val>b.val;
}
然后我们sort一下a也就是按val从大到小排序。和上面的方法有点相似的,初始化为0,每次add(a[i].num,1), ans+= query(a[i].num-1)。这里树状数组维护的是num,因为从大到小排序,所以这个时刻在树状数组里如果为1说明大小比a[i]大。所以query(a[i].num-1)就是原数组中排在a[i]前面的并且比a[i]大的数的个数,这种方法里每次计数的是是以a[i]为末尾的逆序对。
看起来第一种方法比第二种好很多,还省了一次排序。那么如果不限制这些数字的范围呢?比如n<=1e5,-1e9<=a[i]<=1e9?第二种方法仍然能正确地得出结果,而第一种好像就不能直接用了。
其实也是可以的,只要离散化一下。
因为这个问题中,答案与绝对大小无关而只与相对大小有关,比如-1e9,1e9,114514的逆序对数其实就是1,3,2的逆序对数。所以我们可以用上面的结构体把cmp里的>换成<,sort一下a,然后i从1到n循环一次,赋值b[a[i].num]=i,然后b的范围就是1-n,而b数组的逆序对和a是相同的,这样就可以用上面的第一种方法来求逆序对了。
说了这么多,如果用上面的思路去做洛谷的P1908……还是会WA,因为里面的数据允许重复。
上面说的两种做法都没有考虑过重复的情况。第一种方法里,我们认为query(a[i]-1)是所有在原数组中在a[i]前面并且小于a[i]的数的个数,而在离散化的时候,我们赋值b[a[i].num]=i,就可能让原本在a里一样大的数在b里被赋值成不一样大,解决方法也很简单,只要用一个tot来代替i就可以了,如果a[i].val!=a[i-1].val就=tot++,否则= b[a[i-1].num]。并且ans+= i-1-query(a[i])而不是上面的i-1-query(a[i]-1),否则之前已经填入的重复部分会被当做没有填入计数。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5+5;
struct node{
int num,val;
};
bool cmp(node a,node b)
{
return a.val<b.val;
}
int bit[maxn],n;
node a[maxn];
int b[maxn];
inline int lowbit(int x)
{
return x&(-x);
}
void add(int x,int d)
{
while(x<=n)
{
bit[x]+=d;
x+=lowbit(x);
}
}
int sum(int x)
{
int ret = 0;
while(x)
{
ret+=bit[x];
x-=lowbit(x);
}
return ret;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1;i<=n;i++)
{
cin >> a[i].val;
a[i].num=i;
}
sort(a+1,a+1+n,cmp);
int tot = 1;
a[0].val=-1;
for(int i = 1;i<=n;i++){
b[a[i].num]=(a[i].val==a[i-1].val)?b[a[i-1].num]:tot++;
}
long long ans = 0;
for(int i = 1;i<=n;i++)
{
ans+=i-1-sum(b[i]);
add(b[i],1);
}
cout << ans << endl;
}
其实这里也可以用lower_bound来离散化,这样就不需要a[i]是个结构体了。先让把a的内容拷贝到b,然后排序b后a[i]=lower_bound(b+1,b+n+1,a[i])-b;
第二种做法的解决方案更简单。我们认为query(a[i].num-1)是原数组中排在a[i]前面的并且比a[i]大的数的个数,这里出错的唯一原因就是在计数时我们可能会将之前填入树状数组的大小相同的数算进去。只要在从大到小排序的时候顺便从编号大到小排序就不会把编号小的记进去了。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5+5;
struct node{
int num,val;
};
bool cmp(node a,node b)
{
if(a.val!=b.val)return a.val>b.val;
return a.num>b.num;
}
inline int lowbit(int x)
{
return x&(-x);
}
int bit[maxn],n;
node a[maxn];
void add(int x,int d)
{
while(x<=n)
{
bit[x]+=d;
x+=lowbit(x);
}
}
int sum(int x)
{
int ret = 0;
while(x)
{
ret+=bit[x];
x-=lowbit(x);
}
return ret;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1;i<=n;i++)
{
cin >> a[i].val;
a[i].num=i+1;
}
sort(a+1,a+1+n,cmp);
long long ans = 0;
forab(i,1,n)
{
ans+=sum(a[i].num-1);
add(a[i].num,1);
}
cout << ans << endl;
}