HDU 1394 树状数组+离散化求逆序数
对于求逆序数问题,学会去利用树状数组进行转换求解方式,是很必要的。
一般来说我们求解逆序数,是在给定一串序列里,用循环的方式找到每一个数之前有多少个比它大的数,算法的时间复杂度为o(n2)。
那么我们通过树状数组可以明显提高时间效率。
我们可以按照排列的顺序依次将数字放入树状数组中,并依次更新预与之相关联的树状数组元素。那么在将其更新完毕后,我们知道每个数对应的树状数组元素的左边的数肯定比它小,我们在以序列顺序依次更新树状数组时,如果有值在它前面出现,那么它对应的树状数组元素(在这个题目里存放的是个数)值必然增加,我们可以利用树状数组快速求一段区间的总和(这一段是比它小的数字的总个数)。那么用i-sum得到的就是逆序数了。
当然,另外还要注意到一点由于数字可能很大,那么我们开那么大的数组是不合理地,为此我们用离散化的方式来处理r[num[i].tag]=i;(r[MAXN]存放离散数据,即用i=1;i<=t;i++从小到大表示递增的数据,以便充分使用每一个数组元素,类似于map的功能,映射。)
所以因为有这一步,所以要先sort,然后将数值离散化。这样每一个数的更新仅需o(log(n))
总的时间降为o(nlog(n))。
带着代码再加深理解:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #define MAXN 5005 6 using namespace std; 7 struct node 8 { 9 int val; 10 int tag; 11 }num[MAXN]; 12 int t; 13 int r[MAXN],c[MAXN];//c[MAXN]为已经离散化的树状数组元素 14 bool cmp(node a,node b) 15 { 16 return a.val<b.val; 17 } 18 int lowbit(int x) 19 { 20 return x&(-x); 21 } 22 void update(int x) 23 { 24 while(x<=t) 25 { 26 c[x]+=1; 27 x+=lowbit(x); 28 } 29 } 30 int s(int x) 31 { 32 int sum=0; 33 while(x>0) 34 { 35 sum+=c[x]; 36 x-=lowbit(x); 37 } 38 return sum; 39 } 40 int main() 41 { 42 int i,j,ans,min; 43 while(cin>>t) 44 { 45 ans=0; 46 memset(c,0,sizeof(c)); 47 for(i=1;i<=t;i++) 48 { 49 scanf("%d",&num[i].val); 50 num[i].tag=i; 51 } 52 sort(num+1,num+t+1,cmp); 53 for(i=1;i<=t;i++) 54 { 55 r[num[i].tag]=i;//进行离散化 56 } 57 for(i=1;i<=t;i++) 58 { 59 update(r[i]); 60 ans=ans+i-s(r[i]); 61 } 62 min=ans; 63 for(i=1;i<t;i++) 64 { 65 ans=ans-r[i]+1+t-r[i]; 66 min=ans<min?ans:min; 67 } 68 printf("%d\n",min); 69 } 70 return 0; 71 }