(树状数组求逆序数)hdu1394 Minimum Inversion Number
程序员文章站
2022-06-09 19:30:40
...
传送门:hdu1394 Minimum Inversion Number
题解是摘自书上原话,初次看时,没看懂。悟清楚其原理后,感觉表达得非常清晰。
题解:根据逆序对的定义,就是找在它前面比它大的数字的个数的和。此题可以用树状数组来实现,树状数组是可以快速的求出前i项逆序对的和,这个序列已知是从0~n-1的全排列。
从序列的第一个梳子开始,假设该数字是3,n=10。即3是第7大的数字,因而很容易求出前7项的和来记录对于3的逆序对数。因为这里是从第一位开始玩树状数组中加入的,所以这里统计的一定是排在3之前的数字。统计完后,在第7位置上加1即可。一次扫描就可以把一组逆序对求出。
但是如果对于每一组序列都这么做,时间复杂度是O(n^2*logn).题目需求是不允许的。第一组逆序对已经求出。第二组的序列可以看成是把第一个数字放在最后一位上。这样的逆序对数会发生怎样的改变呢?首先,可以求出第一个数字放在最后一位上时能产生的逆序对数(按照前面的方法);其次是可以知道第一位以后的数字有多少个比第一个数字小的数字数目,这就是减少的逆序对数。同归哦这样的转变,就可以通过O(logn)的复杂度来转移。因而,总复杂度是(2nlogn)。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=5002;
int n,s[maxn],num[maxn];
int lowbit(int x){
return x&(-x);
}
void add(int x,int v){
int res=0;
while(x<=n){
s[x]+=v;
x+=lowbit(x);
}
}
int sum(int x){
int res=0;
while(x){
//cout<<"s["<<x<<"]="<<s[x]<<endl;
res+=s[x];
x-=lowbit(x);
}
return res;
}
int main(){
while(~scanf("%d",&n)){
memset(s,0,sizeof(s));
for(int i=0;i<n;i++){
scanf("%d",&num[i]);
}
int tm=0,tp;
for(int i=0;i<n;i++){
//cout<<"n-num[i]="<<n-num[i]<<endl;
tm+=sum(n-num[i]);
add(n-num[i],1);
}
/* for(int i=1;i<=n;i++){
cout<<"s["<<i<<"]="<<s[i]<<endl;
}*/
int ans=tm;
tp=tm;
for(int i=0;i<n-1;i++){
//cout<<"i="<<i<<",tp="<<tp<<endl;
add(n-num[i],-1);
tp+=sum(n-num[i])-num[i]; //计算新的逆序对数,sum(n-num[i])就是由最前面的数挪到最后面,逆序数由0增加的,num[i]则是其余数减少的逆序数,因为其他数比它小的有num[i]个
add(n-num[i],1);
if(ans>tp) ans=tp;
}
printf("%d\n",ans);
}
return 0;
}