POJ 1804 逆序对问题【分治】【线段树】【树状数组】【平衡树】

至于为何冒泡排序的次数,是逆序对的数量?


这里说一下从小到大冒泡的问题。


对于一个数字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 
#include 
#include 
#include 
#include 
using namespace std;

const int max_n = 1000 + 10;

int n;
int a[max_n], count;
mapG;
map::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?"<

时间挺慢的。
1804 Accepted 836K 563MS G++ 2132B



方法3:树状数组, 其实和线段树道理一样。 但是对于树状数组,我会单独开一张好好研究哒。 这里就贴一下速度,并没有比线段树快很多……也许我的写法不好?【如果对树状数组有疑惑,可以看我下一篇文章,我会带领你们好好学会树状数组这个神奇的东西~】

1804 Accepted 620K 547MS G++ 1006B

交了一个别人的树状数组的程序,卧槽比我快那么多. 仔细想想,应该是我的离散化效率不高吧~~~ 毕竟我偷懒用map了呜呜呜~

1804 Accepted 8552K 438MS G++

#include 
#include 
#include 
#include 
using namespace std;
#define lowbit(k) ((k)&(-k))

const int max_n = 1000 + 10;
int n, a[max_n], s[max_n];
mapG;
map::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 
#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<



===============


结论: 从我的程序速度来看,分治太快了,后面的算法基本追不上(平衡树也许有一些其他平衡树可以追上分治的速度)

树状数组和线段树总体而言速度差别不大。 但是平衡树还是要明显快。

你可能感兴趣的:(逆序对问题,POJ)