1. 程式人生 > >樹狀陣列相關應用之逆序對問題

樹狀陣列相關應用之逆序對問題

求逆序對

一元逆序對問題:POJ-2299 在這裡插入圖片描述 在這裡插入圖片描述 此題本質是一個求逆序對問題,對於一個無序數列,我們按照其順序依次輸入,並在每次輸入時通過樹狀陣列對已輸入數列在其後方的序列進行個數求和,即可得到逆序數(先輸入卻比他大,在其後方) 需要注意的是數列的值可能相差較大,如果按值更新樹狀陣列會浪費很多空間,對於此問題我們可以利用資料的離散化來處理

#include <iostream>
#include <cstring>
#include <algorithm>

#define MAXSIZE 500002

using namespace std;   //使用名稱空間std


struct Node
{
	int va;         //下標(用於資料離散化的順序復原)
	int number;     //數字
};

bool cmp(Node a, Node b)
{
	return a.number < b.number;
}

int main()
{
	int lowbit(int x);
	void update(int val[], int i, int cal, int arry_num);
	int sum_pre(int val[], int i);
	int sum_between(int val[], int pre, int last);

	Node arry[MAXSIZE];       //原陣列
	int val[MAXSIZE];        //樹狀陣列
	int aa[MAXSIZE];        //離散化陣列
	memset(val, 0, sizeof(val));
	memset(arry, 0, sizeof(arry));
	//樹狀陣列初始化完畢  

	int n;
	int i;
	long long ans;                          //逆序數對數可能較大,需用long long儲存
	while (cin >> n)
	{
		if (!n)    break;
		ans = 0;                            //逆序數儲存器初始化
		for (i = 1;i <= n;i++)
		{
			cin >> arry[i].number;
			arry[i].va = i;
		}
		//原陣列初始化完成
		sort(arry + 1, arry + n + 1, cmp);    //按數值從小到大排序
		for (i = 1;i <= n;i++)                //按之前的元素下標進行順序復原
			aa[arry[i].va] = i;
		//對應陣列離散化完成

		//樹狀陣列求逆序數
		memset(val, 0, sizeof(val));         //樹狀陣列初始化
		for (i = 1;i <= n;i++)
		{
			update(val, aa[i], 1, n);
			ans +=sum_between(val, aa[i]+1,n);   //對其後方進行求和得到該數對應逆序數個數
		}
		cout << ans << endl;


	}
	return 0;
}

int lowbit(int x)
{//返回二進位制數最低位的1對應的數值
	return x & (-x);      //與運算
}


void update_1(int val[], int i, int cal,int arry_num)
{//原陣列第i個元素加上cal,更新樹狀陣列相關元素,arry_num為原陣列的長度
 //可直接用於樹狀陣列的建立
	for(;i<=arry_num;i+=lowbit(i))
		val[i]+=cal;
}

int sum_pre_1(int val[],int i)
{//求arry陣列的前i項和
 //val為樹狀陣列地址
	int sum = 0;
	for (;i > 0;i -= lowbit(i))       //從後向前每次跳一個lowbit
		sum += val[i];
	return sum;
}

int sum_between_1(int val[],int pre, int last)
{//求原陣列arry在區間[pre-last]的和
	return sum_pre_1(val, last) - sum_pre_1(val, pre - 1);
}

接下來我們來看一道樹狀陣列求逆序對的變形題: 二元逆序對問題:POJ-3067 在這裡插入圖片描述 在這裡插入圖片描述 題意:

日本有N個城市在東邊,從北至南編號為1 2 3,,,N,M個城市在西邊,從北至南編號為1 2 ,,,,M,K條高速公路將被建造

高速公路的一端在西邊,一端在東邊

輸入有多組樣例,

每組樣例第一行為

n m k

接下來有k行,分別為高速公路的端點

求高速公路的交點有多少個,不包括以城市為相交點

解決方法:

對Ax,Ay和Bx,By兩條高速公路,有相交點必須(Ax-Bx)*(Ay-By)<0(本質仍是求逆序對) 做法:先對這些線段做雙關鍵字排序,以x為主關鍵字升序排列,x相同時再以y為次關鍵字升序排列,再逐一插入這些線段,求出在它之前插入的線段中y大於當前線段的y的線段數目,把它累加到答案中,最終就可以得出答案。因為我們知道,如果兩條線段有交點,那麼必有一條線段的x小於另一條線段的x,而y大於另一條線段的y,又因為我們已經對x做了排序,所以只用求出y大於當前線段的y的線段數目即可。另外,由於答案可能會很大,所以需要使用long long

#include <iostream>
#include <cstring>
#include <stdio.h>
#include <algorithm>

#define MAXSIZE 1005

using namespace std;   //使用名稱空間std

struct road
{
	int left;
	int right;
};

road line[MAXSIZE*MAXSIZE];        //路

int main()
{
	bool cmp(road a, road b);
	int lowbit(int x);
	void update_1(int val[], int i, int cal, int arry_num);
	long long sum_pre_1(int val[], int i);
	long long sum_between_1(int val[], int pre, int last);
	int val[MAXSIZE];       //樹狀陣列
	
	int flag, temp=1;         
	int N, M, K;
	long long num;                //交叉點數(long long是個坑)
	scanf("%d", &flag);
	while (temp<=flag)
	{
		num = 0;
		memset(val, 0, sizeof(val));
		scanf("%d %d %d", &N, &M, &K);
		for (int i = 0;i < K;i++)
			scanf("%d %d", &line[i].left, &line[i].right);
		sort(line, line + K, cmp);
		for (int i = 0;i < K;i++)
		{
			update_1(val, line[i].right, 1, M);
			num += sum_between_1(val, line[i].right+1, M);          //求逆序對
		}
		printf("Test case %d: %lld\n", temp, num);
		temp++;
	}
	return 0;
}

bool cmp(road a, road b)
{//將左端小的放在前,如果左端相等,將右端小的放在前
	if (a.left != b.left)
		return a.left < b.left;
	else
		return a.right < b.right;
}

int lowbit(int x)
{//返回二進位制數最低位的1對應的數值
	return x & (-x);      //與運算
}

//一維樹狀陣列
void update_1(int val[], int i, int cal, int arry_num)
{//原陣列第i個元素加上cal,更新樹狀陣列相關元素,arry_num為原陣列的長度
 //可直接用於樹狀陣列的建立
	for (;i <= arry_num;i += lowbit(i))
		val[i] += cal;
}

long long sum_pre_1(int val[], int i)
{//求arry陣列的前i項和
 //val為樹狀陣列地址
	long long sum = 0;
	for (;i > 0;i -= lowbit(i))       //從後向前每次跳一個lowbit
		sum += val[i];
	return sum;
}

long long sum_between_1(int val[], int pre, int last)
{//求原陣列arry在區間[pre-last]的和
	return sum_pre_1(val, last) - sum_pre_1(val, pre - 1);
}