Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 10556 Accepted Submission(s): 6510
10 1 3 6 9 0 8 5 7 4 2
16
题目大意:
给你一个序列 a1,a2……an,计算出该序列的逆序数,将第一个数a1移到序列末尾,变为a2,a3……an,a1计算出逆序数。之后再将第一个数a2移到序列末尾,计算出逆序数。依次进行移位,计算逆序数,直到序列变为an,a1,a2,……an-1
找出上述所有序列中最小的逆序数。
思路:
刚看题目的时候,不知道要怎么计算逆序数。参考了HH大神的代码才明白。
先建立一个0-N-1的线段树,代表0-N-1个数的区间,按最初序列从前往后的顺序向线段树中插入数值,比如数值为3就插入到线段树中叶子节点为[3,3]的节点上,节点赋值为1,每插入一次a[i],计算一下a[i]+1到N-1区间和,代表着a[i]的逆序数,具体可自己画一下图表就明白了,a[i]+1到N-1的区间和,代表着之前插入的数值有多少个比a[i]大的,也就是a[i]的逆序数。
将a0-aN-1的逆序数加起来就是最初序列的逆序数。
之后,进行移位的逆序数运算。因为我们已经得到了最初序列的逆序数,就可以根据一步运算得到一次移位后的逆序数,所以就不用再重新建立线段树,计算移位后的逆序数了。
那么将第一个数移到最后,逆序数应该增加或减少多少呢?
假设第一个数为a[i],因为是0~N-1的数,那么a[i]后边有(N-a[i]-1)个比a[i]大的数,有a[i]个比a[i]小的数,移位后,逆序数前者增加N-a[i]-1个,后者减少a[i]个。则逆序数总共增加N-a[i]-1-a[i],最后将a[i]遍历一下,找到最小的逆序数即可。
# include<iostream> # include<algorithm> using namespace std; const int MAXN = 5010; int sum[MAXN<<2]; void pushup(int root) { sum[root] = sum[root<<1] + sum[root<<1|1]; } void build(int root,int L,int R) { sum[root] = 0; if(L==R) return; int mid = (L+R)>>1; build(root<<1,L,mid); build(root<<1|1,mid+1,R); } void updata(int root,int L,int R,int i,int v) { if(L==R) { sum[root]++; return; } int mid = (L+R)>>1; if(i <= mid) updata(root<<1,L,mid,i,v); else updata(root<<1|1,mid+1,R,i,v); pushup(root); } int query(int root,int L,int R,int s,int e) { if(s==L && e==R) return sum[root]; int mid = (L+R)>>1; int res = 0; if(e <= mid) res += query(root<<1,L,mid,s,e); else if(s > mid) res += query(root<<1|1,mid+1,R,s,e); else { res += query(root<<1,L,mid,s,mid); res += query(root<<1|1,mid+1,R,mid+1,e); } return res; } int x[MAXN]; int main() { int n; while(~scanf("%d", &n)) { build(1,0,n-1); int sum = 0; for(int i = 0; i < n; i++) { scanf("%d", &x[i]); sum += query(1,0,n-1,x[i],n-1); updata(1,0,n-1,x[i],1); } int res = sum; for(int i = 0; i < n; i++) { sum += n - x[i] -x[i] -1; res = min(res, sum); } printf("%d\n",res); } return 0; }