逆序对定义:
对于一个序列 a,若 i < j 且 a[i] > a[j],则称 a[i] 与 a[j] 构成逆序对
如何使用树状数组求序列逆序对个数:
任意给定一个集合 a,如果用 c[val] 保存数值 val 在集合 a 中出现的次数,那么数组 c 在 [l,r] 上的区间和就表示集合 a 在范围 [l,r]内的数有多少个。
- 在序列 a 的数值范围上建立树状数组 c[N],初始化全为零。
- 倒序扫描给定的序列 a,对于每个数 a[i]:
1.在树状数组中查询前缀和[1,a[i]-1](我们只需知道在a[i]之前有多少个数,就知道有多少个逆序对),累加到答案中。
2.执行“单点增加”操作,即把位置 a[i] 上的数加1(相当于c[a[i]]++,a[i]这个数出现次数加一),同时正确维护 c 的前缀和。 - ans即为所求
for(int i=n;i>=1;i--)
{
ans+=ask(a[i]-1);//因为查询的是a[i]-1,所以该行代码可与add操作互换位置
add(a[i],1);
}
根据以上思路,得到如下代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
ll n,a[500005],c[500005],maxn;
ll ask(ll x)
{
ll ans=0;
for(; x; x-=x&-x)
{
ans+=c[x];
}
return ans;
}
void add(ll x,ll y)
{
for(; x<=maxn; x+=x&-x)//注意<=maxn
{
c[x]+=y;
}
}
int main()
{
while(cin>>n,n)
{
maxn=0;
for(ll i=1; i<=n; i++)
{
scanf("%lld",&a[i]);
a[i]+=1;
maxn=max(maxn,a[i]);//记录最大的数maxn,因为上面提到树状数组是在数值范围上建立的
}
ll ans=0;
for(ll i=n; i; i--)
{
add(a[i],1);
ans+=ask(a[i]-1);
}
cout<<ans<<endl;
memset(c,0,sizeof(c));//注意初始化
}
return 0;
}
测下样例,完美!交一发,RE?
原来这题a[i]的数据范围特别大,树状数组也无法开到这么大的空间,所以得先对数据离散化处理
我们可以再开个数组d[N]用来记录a[i],然后将d[N]从小到大排序,再用lower_bound()函数进行查询,得到a[i]的相对位置(数组下标)
AC代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<set>
using namespace std;
typedef long long ll;
ll n,a[500005],c[500005],d[500005],maxn;
ll ask(ll x)
{
ll ans=0;
for(; x; x-=x&-x)
{
ans+=c[x];
}
return ans;
}
void add(ll x,ll y)
{
for(; x<=n; x+=x&-x)//注意这里树状数组的数值范围是n
{
c[x]+=y;
}
}
int main()
{
while(cin>>n,n)
{
for(ll i=1; i<=n; i++)
{
scanf("%lld",&a[i]);
d[i]=a[i];
}
ll ans=0;
sort(d+1,d+n+1);
for(ll i=n; i>=1; i--)
{
int x=lower_bound(d+1,d+n+1,a[i])-d;
add(x,1);
ans+=ask(x-1);
}
cout<<ans<<endl;
memset(c,0,sizeof(c));
}
return 0;
}
当然也可以开个结构体,然后排序,总之离散化的方法有很多。还有数据范围要开到ll