ACM 樹狀陣列(逆序対和三種離散化詳解)

2021-10-08 13:29:26 字數 4743 閱讀 5181

逆序対的另外一種解法是歸併排序

下面兩題的離散化方法會得到不同的陣列!!

關於逆序対

已知陣列的逆序対數目=n,則將陣列中相鄰元素交換n次即可得有序陣列

題目:

算出給定的一段正整數序列中逆序對的數目,序列中可能有重複數字

方法一(5.05s)

sort()、unique()、lower_bound()三部曲,得到as[i]表示a[i]是第as[i]小的數字

fo

(i,0

,n)inn

(a[i]

),b[i]

=a[i]

;sort

(b,b+n)

;int l=

unique

(b,b+n)

-b; \\去重,l為不重複元素個數,執行後b[

:l]內都為不重複元素

fo(i,

0,n)

as[i+1]

=lower_bound

(b,b+l,a[i]

)-b+

1; \\二分求a[i]排第幾,+

1是因為b[

]從0開始

排序前

a=12, 36, 55, 36, -10

b=12, 36, 55, 36, -10

去重後l=4

b=-10, 12, 36, 55

二分後as=2, 3, 4, 3, 1

as[i]表示a[i]是第as[i]小的數字,如as[1]=2表示a[1]是第2小的數字

有點像大小關係的一種對映,這樣可以把as[i]當做陣列的下標,既能節省空間,還能防止負數作為下標

#include

#define fo(i,a,b) for(int i=a;i#define lowbit(i) ((i)&-(i))

using

namespace std;

inline

void

inn(

int& x)

while

(ch >=

'0'&& ch <=

'9')

x *= f;

}const

int n=5*

1e5+5;

int tr[n]

,n,m,a[n]

,b[n]

;long

long ans;

inline

void

add(

int x,

int k)

}inline

long

long

sum(

int x)

return num;

}int

main()

cout

}

方法二(2.47s)

結構體+sort+手動去重

所得as[i]表示a[i]是第as[i]小的數字,同方法一

#include

#define lowtr(i) ((i)&-(i))

using

namespace std;

inline

void

inn(

int& x)

while

(ch >=

'0'&& ch <=

'9')

x *= f;

}const

int n=5*

1e5+5;

struct numbera[n]

;int n,as[n]

,tr[n]

;bool

cmp(number a,number b)

inline

void

add(

int i,

int k)

inline

intsum

(int i)

intmain()

cout

}

題目:

兩盒火柴,每盒裝有 n 根火柴

每盒中的火柴各自排成一列, 同一列火柴的高度互不相同

兩列火柴之間的距離定義為:∑(a_i-b_i)^2

每列火柴中相鄰兩根火柴的位置可以交換,為兩列火柴之間的距離最小,最少交換多少次

由σ[(ai-bi)2] = σ(ai2-2 * ai * bi + bi2) ,故求σ(2 * ai * bi)最大即可

順序之乘》=亂序之乘(兩陣列同為公升序or降序時σ(ai*bi)最大)

所以先將兩列火柴存入分別陣列a、b,把陣列離散化後得到陣列as、bn,as[i]表示第i小的數字是a[as[i]]

由於陣列中元素各不相同,可以用

fo

(i,1

,n+1

)inn

(a[i]

),as[i]

=i;sort

(as+

1,as+

1+n,cmp1)

;bool

cmp1

(int x,

int y)

進行排序

例如:

排序前a=12, 55, 36, -10

as=1, 2, 3, 4

排序後a=12, 55, 36, -10

as=4, 1, 3, 2

as[i]表示第i小的數字是a[as[i]],比如as[1]=4表示第1小的數字是a[4]

有點像大小關係的一種對映,這樣可以把as[i]當做陣列的下標,既能節省空間,還能防止負數作為下標

得到陣列as、bn後,通過fo(i,1,n+1) re[as[i]]=bn[i];確定對應關係,再計算re中的逆序対即為交換次數

原因是已知陣列的逆序対數目=n,則將陣列中相鄰元素交換n次即可得有序陣列

#include

#define fo(i,a,b) for(int i=a;iusing

namespace std;

typedef

long

long ll;

inline

void

inn(

int& x)

while

(ch >=

'0'&& ch <=

'9')

x *= f;

}const

int n=

1e5+5;

int n,a[n]

,b[n]

,as[n]

,bn[n]

,tr[n]

,re[n]

;ll ans;

bool

cmp1

(int x,

int y)

bool

cmp2

(int x,

int y)

inline

void

add(

int x)

inline ll sum

(int x)

intmain()

printf

("%lld\n"

,ans)

;return0;

}

另附一題

神奇的字串-樹狀陣列

對兩個只包含字母的長度為n的字串s1,s2執行m次操作

0 l r 代表 詢問[ l , r ] 這個區間配對的個數

1 id p c 如果id 是1,代表把s1字串p位置置為c,如果id 是2,代表把s2字串p位置置為c

#include

#define fo(i,a,b) for(int i=a;i#define lowbit(i) ((i)&-(i))

using

namespace std;

inline

void

inn(

int& x)

while

(ch >=

'0'&& ch <=

'9')

x *= f;

}const

int n=5*

1e5+5;

int tr[n]

,n,m;

inline

void

add(

int x,

int k)

}inline

intsum

(int x)

return num;

}int

main()

else

printf

("%d\n"

,sum

(y)-

sum(x-1)

);}}

return0;

}

詳解樹狀陣列三種模型

首先說明下 最後的最大值模型的 沒有測試,不過應該是沒問題的。其它三個更新求和的模型的 借鑑於網上的各個博文,應該是沒問題的。其中前兩個模型的 已測試無誤。樹狀陣列與線段樹在思想上很類似的一種資料結構,它比線段樹更簡潔,但它的適用範圍也小了些。提供一篇博文,詳解樹狀陣列的 樹狀陣列是乙個可以高效的進...

樹狀陣列的三種模式

我們在這之前已經了解過了lowbit的概念,還有對於樹狀陣列的基本的認識了,然後,再學習樹狀陣列的基礎用法。樹狀陣列主要有三種型別,從簡單到複雜。依次是修改點,求區間和 修改區間求點,還有最複雜的修改區間,求區間。最簡單的第一種模型 int lowbit int x void add int x,i...

樹狀陣列區間求和三種模型

樹狀陣列在區間求和問題上有大用,其三種複雜度都比線段樹要低很多 有關區間求和的問題主要有以下三個模型 以下設a 1.n 為乙個長為n的序列,初始值為全0 1 改點求段 型,即對於序列a有以下操作 修改操作 將a x 的值加上c 求和操作 求此時a l.r 的和。這是最容易的模型,不需要任何輔助陣列。...