至于为何冒泡排序的次数,是逆序对的数量?
这里说一下从小到大冒泡的问题。
对于一个数字k(一开始在数组的k的位置), 在冒泡排序中,只有出现有一个数字在a[k]前面,并且比a[k]要大,这个数字才会交换位置。并且只会向前交换。
显然,对于排序结束后的数列, a[k]前面是不会有比他大的数字了, 同时!a[k]只会和在他前面,比他大的数字交换(冒泡排序中,交换位置的判定。) 那么,交换的次数,就是a[k]在初始序列中,前面有多少个数字比他大。 这也就是逆序对的问题了。
方法1:分治
思考归并排序, 对2个已经排好序的数列,进行再排序,只需要把2个数列,从头到尾,按顺序,谁小,谁就先进入tmp数组, 最后tmp数组一定排好序了,然后把TMP数组的元素复制回原数组中即可。
同理,如果我们知道2个子序列的逆序对数量,是否可以通过归并排序一样,求出整体的数量呢?显然是可以的。
这里有一个地方,当左边的数列的a[k]要进tmp数组了, 这个时候,如果右边的指针指向a+mid+p,就说明a[k]比a[mid+1]...a[mid + 2]..a[mid+3].....a[mid+p]都要大!【重要】
也就是说,对于a[k]而言,整个数列中, mid+ mid+2...mid+p都在k后面,同时a[k]比a[mid+1],a[mid+2]...a[mid+p]都要大。 那么显然是增加逆序对数量的。 通过整个方法,计算出整个逆序对的数量即可。
#include <iostream> #include <cstdio> #include <cstdlib> using namespace std; const int max_n = 1000 + 10; int n, a[max_n]; int tmp[max_n], ans; void merge(int *a, int *tmp, int l, int mid, int r) { if (l >= r) return; int i = l, j = mid + 1, k = 0; int count = 0, flag = 0; while (i <= mid && j <= r) { if (a[i] <= a[j]) { tmp[k ++] = a[i++]; ans += j - mid - 1; }else tmp[k ++ ] = a[j++]; } while (i <= mid) tmp[k ++] = a[i++], ans += r- mid; while (j <= r) tmp[k ++] = a[j++]; for (i = 0; i != k; ++ i) a[l + i] = tmp[i]; } void mergesort(int *a, int *tmp, int l, int r) { if (l >= r) return; int mid = (l + r) / 2; mergesort(a, tmp, l, mid); mergesort(a, tmp , mid + 1, r); merge(a, tmp, l, mid, r); } int main() { int tt; scanf("%d", &tt); for (int i = 1; i <= tt; ++ i) { cout<<"Scenario #"<<i<<":"<<endl; scanf("%d", &n); ans = 0; for (int i = 0; i != n; ++ i) scanf("%d", &a[i]); mergesort(a, tmp, 0, n - 1); cout<<ans<<endl<<endl; } }
1804 | Accepted | 736K | 32MS | G++ | 1050B |
方法2:
线段树
a[L,R] 表示 L,R区间数字数量的个数。 用线段树表示。
那么如果有5要进去, 那么即让[5,max]数量+1, 以后如果需要查比3大的数字,只要查找[4,max]区间有多少个数字即可。 按照逆序对的定义来运行即可。
要离散化!不离散化我爆内存了……
【炸内存】
#include <iostream> #include <cstdio> #include <cstdlib> using namespace std; const int max_n = 1000 + 10; const int max_num = 2000000 + 5; int n; int a[max_n]; struct node { int L, R; int key; int cd; //当前节点的儿子,全部要变动CD那么多 那么当前节点已经变动过了 int ls,rs; void clear() { cd = 0; key = 0; } node():key(0),cd(0),L(-1),R(-1), ls(-1), rs(-1){} }t[max_num * 2 + 200]; int tail=0; void make_tree(int now, int LL, int RR) { t[now].L = LL; t[now].R = RR; if (LL == RR) return; int mid = (LL + RR) / 2; t[now].ls = ++ tail; make_tree(tail, LL, mid); t[now].rs = ++ tail; make_tree(tail, mid + 1, RR); } void tran(int now) { int left_son = t[now].ls, right_son = t[now].rs; if (left_son != -1) { t[left_son].cd += t[now].cd; t[right_son].cd += t[now].cd; } t[now].key += t[now].cd; t[now].cd = 0; } void ins_tree(int now, int LL, int RR) { tran(now); if (t[now].L == LL && t[now].R == RR) { t[now].cd ++; return; } t[now].key ++; int mid = (t[now].L + t[now].R) / 2; if (RR <= mid) {ins_tree(t[now].ls, LL, RR); return;} if (mid < LL) {ins_tree(t[now].rs, LL, RR); return; } ins_tree(t[now].ls, LL, mid); ins_tree(t[now].rs, mid + 1, RR); } int find_tree(int now, int LL, int RR) { tran(now); if (t[now].L == LL && t[now].R == RR) return t[now].key; int mid = (t[now].L + t[now].R) / 2; if (RR <= mid) return find_tree(t[now].ls, LL, RR); if (mid < LL) return find_tree(t[now].rs, LL, RR); return find_tree(t[now].ls, LL, mid) + find_tree(t[now].rs, mid + 1, RR); } int main() { int tt, tmp; ios::sync_with_stdio(false); scanf("%d", &tt); make_tree(0, 0, 2000010); for (int i = 1; i <= tt;++i) { int ans = 0; cout<<"Scenario #"<<i<<":"<<endl; cin >> n; for (int i = 0; i != max_num * 2 + 200; ++ i) t[i].clear(); for (int i = 0; i != n; ++ i) { cin>>tmp; tmp += 1000002; ans += find_tree(0, tmp + 1, tmp + 1); ins_tree(0, 0, tmp); } cout<<ans<<endl; } return 0; }
【AC】
#include <map> #include <iostream> #include <set> #include <cstdio> #include <cstdlib> using namespace std; const int max_n = 1000 + 10; int n; int a[max_n], count; map<int, int>G; map<int, int>::iterator it; struct node { int cd, key; int ls, rs; int L, R; node():L(0),R(0),ls(0),rs(0),cd(0),key(0){}; void clear() { cd = key = 0; } }t[max_n * 3]; int tail = 1; void init() { for (int i = 0; i != max_n * 3; ++ i) t[i].clear(); G.clear(); scanf("%d", &n); for (int i = 0; i != n; ++ i) { scanf("%d", &a[i]); G[a[i]] = 0; } count = 0; for (it = G.begin(); it != G.end(); ++ it) it -> second = ++ count; } void make_tree(int now, int LL, int RR) { t[now].L = LL; t[now].R = RR; if (LL == RR) return; int mid = (LL + RR)/ 2; make_tree(t[now].ls = ++ tail, LL, mid); make_tree(t[now].rs = ++ tail, mid + 1, RR); } void tran(int now) { int left_son = t[now].ls, right_son = t[now].rs; t[left_son].cd += t[now].cd; t[right_son].cd += t[now].cd; t[now].key += t[now].cd; t[now].cd = 0; } void ins(int now, int LL, int RR) { tran(now); if (t[now].L == LL && t[now].R == RR) { t[now].cd ++; return; } t[now].key ++; int mid = (t[now].L + t[now].R) / 2; if (RR <= mid) {ins(t[now].ls, LL, RR); return;} if (mid < LL) {ins(t[now].rs, LL, RR); return;} ins(t[now].ls, LL, mid); ins(t[now].rs, mid + 1, RR); } int find(int now, int LL, int RR)//因为题目的特殊性,只会找一个…… { tran(now); if (t[now].L == LL && t[now].R == RR) return t[now].key; int mid = (t[now].L + t[now].R) / 2; if (RR <= mid) return find(t[now].ls, LL, RR); if (mid < LL) return find(t[now].rs, LL, RR); cout<<"wtf?"<<endl; } void doit() { int ans=0; for (int i = 0; i != n; ++ i) { int num = G[a[i]]; ans += find(1, num + 1, num + 1); ins(1, 0, num); } cout<<ans<<endl; } int main() { int tt; scanf("%d",&tt); make_tree(1, 0, 1002); for (int i = 1; i <= tt; ++ i) { cout<<"Scenario #"<<i<<":"<<endl; init(); doit(); cout<<endl; } }
1804 | Accepted | 836K | 563MS | G++ | 2132B |
方法3:树状数组, 其实和线段树道理一样。 但是对于树状数组,我会单独开一张好好研究哒。 这里就贴一下速度,并没有比线段树快很多……也许我的写法不好?【如果对树状数组有疑惑,可以看我下一篇文章,我会带领你们好好学会树状数组这个神奇的东西~】
1804 | Accepted | 620K | 547MS | G++ | 1006B |
交了一个别人的树状数组的程序,卧槽比我快那么多. 仔细想想,应该是我的离散化效率不高吧~~~ 毕竟我偷懒用map了呜呜呜~
1804 | Accepted | 8552K | 438MS | G++ |
#include <cstdio> #include <cstdlib> #include <map> #include <cstring> using namespace std; #define lowbit(k) ((k)&(-k)) const int max_n = 1000 + 10; int n, a[max_n], s[max_n]; map<int, int>G; map<int, int>::iterator it; int count; void init() { scanf("%d", &n); G.clear(); count = 1; memset(s, 0, sizeof(s)); for (int i = 0; i != n; ++ i) { scanf("%d", &a[i]); G[a[i]] = 0; } for (it = G.begin(); it != G.end(); ++ it) it -> second = ++ count; } void ins(int k) { s[k] += 1; while ((k += lowbit(k)) <= 1000) s[k] += 1; } int ask(int k)//1..k的和 { int tot = s[k]; while (k -= lowbit(k)) tot += s[k]; return tot; } void doit() { int ans = 0; for (int i = 0; i != n; ++ i) { int num = G[a[i]]; ans += ask(count) - ask(num); ins(num); } printf("%d\n",ans); } int main() { int tt; scanf("%d", &tt); for (int i = 1; i <= tt; ++ i) { printf("Scenario #%d:\n",i); init(); doit(); printf("\n"); } }
方法4:平衡树
只要查找,当前在树中,有多少个数字比a[k]要大, 因为是按顺序插入的,所以这个数字的数量就是逆序对的个数
这里有一个小技巧,如果平衡树每次要删的话很麻烦,直接用写成struct,然后新的树就new,最后delete掉即可~
1804 | Accepted | 2656K | 125MS | G++ | 2622B |
#include <iostream> #include <cstdio> #include <cstdlib> using namespace std; const int max_n = 1000 + 10; int n; const int maxint = 0x7fffffff; struct node { node *c[2]; int key; int size; node():key(0),size(0) { c[0] = c[1] = this; } node(int KEY_, node *a0, node *a1): key(KEY_){c[0] =a0, c[1]=a1;} node* rz(){return size = c[0]->size + c[1]->size + 1, this;} }Tnull, *null=&Tnull; struct splay { node *root; splay() { root = (new node(*null)) -> rz(); root -> key = maxint; } void zig(int d) { node *t = root -> c[d]; root -> c[d] = null -> c[d]; null -> c[d] = root; root = t; } void zigzig(int d) { node *t = root -> c[d] -> c[d]; root -> c[d] -> c[d] = null -> c[d]; null -> c[d] = root -> c[d]; root -> c[d] = null -> c[d] -> c[!d]; null -> c[d] -> c[!d] = root -> rz(); root = t; } void finish(int d) { node *t = null -> c[d], *p = root -> c[!d]; while (t != null) { t = null -> c[d] -> c[d]; null -> c[d] -> c[d] = p; p = null -> c[d] -> rz(); null -> c[d] = t; } root -> c[!d] = p; } void select(int k)//谁有k个儿子 { int t; while (1) { bool d = k > (t = root -> c[0] -> size); if (k == t || root -> c[d] == null) break; if (d) k -= t + 1; bool dd = k > (t = root -> c[d] -> c[0] -> size); if (k == t || root -> c[d] -> c[dd] == null){zig(d); break;} if (dd) k -= t + 1; d != dd ? zig(d), zig(dd) : zigzig(d); } finish(0), finish(1); root -> rz(); } void search(int x) { while (1) { bool d = x > root -> key; if (root -> c[d] == null) break; bool dd = x > root -> c[d] -> key; if (root -> c[d] -> c[dd] == null){zig(d); break;} d != dd ? zig(d), zig(dd) : zigzig(dd); } finish(0), finish(1); root -> rz(); if (x > root -> key) select(root -> c[0] -> size + 1); } void ins(int x) { search(x); node *oldroot = root; root = new node(x, oldroot -> c[0],oldroot); oldroot -> c[0] = null; oldroot -> rz(); root -> rz(); } int sel(int k){return select(k - 1), root -> key;} int ran(int x){return search(x), root -> c[0] -> size + 1;} }*sp; int main() { int tt; scanf("%d", &tt); for (int i = 1; i <= tt; ++ i) { sp = new splay; cout<<"Scenario #"<<i<<":"<<endl; scanf("%d", &n); int ans = 0; int tmp; for (int i = 0; i != n; ++ i) { scanf("%d", &tmp); tmp = - tmp; ans += sp -> ran(tmp) - 1; //cout<<sp.ran(tmp) - 1<<endl; sp -> ins(tmp); } delete sp; cout<<ans<<endl<<endl; } }
===============
结论: 从我的程序速度来看,分治太快了,后面的算法基本追不上(平衡树也许有一些其他平衡树可以追上分治的速度)
树状数组和线段树总体而言速度差别不大。 但是平衡树还是要明显快。