至于为何冒泡排序的次数,是逆序对的数量?
这里说一下从小到大冒泡的问题。
对于一个数字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
#include
#include
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 #"<
1804 | Accepted | 736K | 32MS | G++ | 1050B |
方法2:
线段树
a[L,R] 表示 L,R区间数字数量的个数。 用线段树表示。
那么如果有5要进去, 那么即让[5,max]数量+1, 以后如果需要查比3大的数字,只要查找[4,max]区间有多少个数字即可。 按照逆序对的定义来运行即可。
要离散化!不离散化我爆内存了……
【炸内存】
#include
#include
#include
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 #"<> 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<
【AC】
#include
1804 | Accepted | 836K | 563MS | G++ | 2132B |
方法3:树状数组, 其实和线段树道理一样。 但是对于树状数组,我会单独开一张好好研究哒。 这里就贴一下速度,并没有比线段树快很多……也许我的写法不好?【如果对树状数组有疑惑,可以看我下一篇文章,我会带领你们好好学会树状数组这个神奇的东西~】
1804 | Accepted | 620K | 547MS | G++ | 1006B |
交了一个别人的树状数组的程序,卧槽比我快那么多. 仔细想想,应该是我的离散化效率不高吧~~~ 毕竟我偷懒用map了呜呜呜~
1804 | Accepted | 8552K | 438MS | G++ |
#include
#include
#include
方法4:平衡树
只要查找,当前在树中,有多少个数字比a[k]要大, 因为是按顺序插入的,所以这个数字的数量就是逆序对的个数
这里有一个小技巧,如果平衡树每次要删的话很麻烦,直接用写成struct,然后新的树就new,最后delete掉即可~
1804 | Accepted | 2656K | 125MS | G++ | 2622B |
#include
#include
#include
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 #"< ran(tmp) - 1;
//cout< ins(tmp);
}
delete sp;
cout<
===============
结论: 从我的程序速度来看,分治太快了,后面的算法基本追不上(平衡树也许有一些其他平衡树可以追上分治的速度)
树状数组和线段树总体而言速度差别不大。 但是平衡树还是要明显快。