题意:问给一堆数排序要交换多少次……这个排序方法读题的时候没看出来是什么……后来百度了一下说是冒泡,也就是说这是一个求逆序数的题。
解法:求逆序数有三种方法,线段树,树状数组,归并排序。以上三种方法是按我做的顺序排的,实际最优的是归并和树状数组,线段树有常数。
线段树:首先要离散化处理,离散化的方法是给数字标上序号,再进行排序,排序后的序号序列即为离散化后的序列。然后要求逆序数就要看每一位数之前的数中有几个比自己大的,所以可以边插入边查询,记录哪个数字出现过,出现为1没出现为0(这道题貌似所有数字都是不同的,如果有相同数字应当记录出现的次数),用线段树维护区间和,即可以知道某个区间内的数字出现了多少个,对于当前数字来说只要查询当前数字到n这段区间内出现多少数字就可以知道有多少个比自己大的数出现过了。
代码:
#include<stdio.h> #include<iostream> #include<algorithm> #include<string> #include<string.h> #include<math.h> #include<limits.h> #include<time.h> #include<stdlib.h> #include<map> #include<queue> #include<set> #include<stack> #include<vector> #define LL long long using namespace std; #define lson l, m, rt << 1 #define rson m + 1, r, rt << 1 | 1 int sum[500005 << 2]; struct node { int num, no; bool operator < (const node &tmp) const { if(num == tmp.num) return no < tmp.no; return num < tmp.num; } }num[500005]; void pushUp(int rt) { sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; } void update(int pos, int l, int r, int rt) { if(l == r) { sum[rt] = 1; return ; } int m = (l + r) >> 1; if(pos <= m) update(pos, lson); else update(pos, rson); pushUp(rt); } int query(int ll, int rr, int l, int r, int rt) { if(ll <= l && rr >= r) return sum[rt]; int m = (l + r) >> 1; int res = 0; if(ll <= m) res += query(ll, rr, lson); if(rr > m) res += query(ll, rr, rson); return res; } int main() { int n; while(~scanf("%d", &n) && n) { memset(sum, 0, sizeof sum); for(int i = 1; i <= n; i++) { scanf("%d", &num[i].num); num[i].no = i; } sort(num + 1, num + n + 1); LL ans = 0; for(int i = 1; i <= n; i++) { ans += query(num[i].no, n, 1, n, 1); update(num[i].no, 1, n, 1); } printf("%lld ", ans); } return 0; }
树状数组:思路和线段树相同,维护区间和,但时间减少了一半。
代码:
#include<stdio.h> #include<iostream> #include<algorithm> #include<string> #include<string.h> #include<math.h> #include<limits.h> #include<time.h> #include<stdlib.h> #include<map> #include<queue> #include<set> #include<stack> #include<vector> #define LL long long using namespace std; int sum[500005]; struct node { int num, no; bool operator < (const node &tmp) const { if(num == tmp.num) return no < tmp.no; return num < tmp.num; } }num[500005]; int n; inline int lowbit(int x) { return x & (-x); } void update(int pos) { for(int i = pos; i <= n; i += lowbit(i)) sum[i] += 1; } int getsum(int pos) { int res = 0; for(int i = pos; i > 0; i -= lowbit(i)) res += sum[i]; return res; } int main() { while(~scanf("%d", &n) && n) { memset(sum, 0, sizeof sum); for(int i = 1; i <= n; i++) { scanf("%d", &num[i].num); num[i].no = i; } sort(num + 1, num + n + 1); LL ans = 0; for(int i = 1; i <= n; i++) { ans += getsum(n) - getsum(num[i].no); update(num[i].no); } printf("%lld ", ans); } return 0; }
归并排序:每次合并两段排好序的区间,当后面的区间有数字插入到前一区间的某数字之前时,例1 3 4 7和2 5 6 8,2要插入到3前面,则相当于冒泡中和7, 4, 3分别交换位置,所以2排进去的时候交换了3次,同理5交换1次,6交换1次,8交换0次。
代码:
#include<stdio.h> #include<iostream> #include<algorithm> #include<string> #include<string.h> #include<math.h> #include<limits.h> #include<time.h> #include<stdlib.h> #include<map> #include<queue> #include<set> #include<stack> #include<vector> #define LL long long using namespace std; int a[500010], ta[500010]; LL ans = 0; void mergesort(int l, int r) { if(l >= r) return ; int m = (l + r) >> 1; mergesort(l, m); mergesort(m + 1, r); int p1 = l, p2 = m + 1, p3 = l;//p1为前一区间指针,p2为后一区间指针,p3为临时数组ta指针 while(p1 <= m || p2 <= r) { if(p2 > r || (p1 <= m && a[p1] <= a[p2]))//当后一区间排完或者前一区间未排完且前一区间当前数比后一区间当前数小时(之前没写p1 <= m这个条件一直RE……) ta[p3++] = a[p1++]; else//当前一区间排完或后一区间未排完且前一区间的数较大时 { if(p1 <= m) ans += (m + 1 - p1); ta[p3++] = a[p2++]; } } for(int i = l; i <= r; i++) a[i] = ta[i]; } int main() { int n; while(~scanf("%d", &n) && n) { for(int i = 0; i < n; i++) scanf("%d", &a[i]); ans = 0; mergesort(0, n - 1); printf("%lld ", ans); } return 0; }