题目:hdu1394 Minimum Inversion Number
逆序数:在数组a中,满足 且 的两个数
个人感觉求逆序数用树状数组更方便一些,不过使用线段树也是可以的
使用线段树求一个数组中逆序数的组数方法:(注:和树状数组求逆序数的方法很像,数组中元素互不相同)
1、对于数组中元素很大而数组元素个数不多的情况下,可以先进行离散化
2、按照数组从1到n逐个插入到线段树中,线段树结点表示区间中已经插入的数的个数
3、每插入一个数x,在线段树中查询比x大的数的个数,ans加上这个数,然后将数x插入到线段树中
4、当数组中的所有的数都插入到线段树时,ans就是这个数组的逆序数的组数
时间复杂度:
这道题的难点不是怎么求逆序数,而是怎么快速求出n个数组中的最小逆序数?
对于给出的数组:a[1]、a[2]、a[3] …… a[n](记此时数组的逆序数为s)
将a[1]放到末尾后:a[2]、a[3] …… a[n]、a[1](记此时数组的逆序数为ss)
ss和s有什么关系呢?
我们考虑a[1]对s的贡献:即数组从2到n中所有比a[1]小的数的个数
当将a[1]移动到数组末尾后:原来比a[1]小的数不能与a[1]构成逆序数,相反,原来比a[1]大的数与a[1]构造逆序数
所以:(low[1]表示在a[1]的位置后面的所有数中,比a[1]小的数的个数,up[1]表示比a[1]大的数的个数,因为每次操作的总是第一个元素,所以第一个元素后面的所有数也就是数组中所有的数,预处理即可)
现在我们只需要不断将第一个元素放到末尾,然后更新答案即可,时间复杂度:
代码实现:
#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
int a[5009];
int t[5009*4];
int n;
int ans=0;
void update(int rt,int l,int r,int x,int val)
{
if(l==r&&l==x)
{
t[rt]+=val;
return ;
}
int mid=(l+r)>>1;
if(x<=mid)
update(rt<<1,l,mid,x,val);
else
update(rt<<1|1,mid+1,r,x,val);
t[rt]=t[rt<<1]+t[rt<<1|1];
}
int query(int rt,int l,int r,int x,int y)
{
if(x<=l&&r<=y)
return t[rt];
int mid=(l+r)>>1;
int sum=0;
if(x<=mid)
sum+=query(rt<<1,l,mid,x,y);
if(y>mid)
sum+=query(rt<<1|1,mid+1,r,x,y);
return sum;
}
int main()
{
cin.sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
while(cin>>n)
{
memset(t,0,sizeof(t));
int sum=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
a[i]+=1;//将数组范围变成1-n
sum+=query(1,1,n,a[i]+1,n);
update(1,1,n,a[i],1);
}
ans=sum;
for(int i=1;i<=n;i++)
{
sum=sum+n-2*a[i]+1;
ans=min(ans,sum);
}
cout<<ans<<'\n';
}
return 0;
}