loj #535. 「LibreOJ Round #6」花火 樹狀數組求逆序對+主席樹二維數點+總體二分

$ \color{#0066ff}{ 題目描述 }$

「Hanabi, hanabi……」node

一據說祭典上沒有煙火,Karen 一臉沮喪。c++

「有的哦…… 雖然比不上大型煙花就是了。」git

還好 Shinobu 早有準備,Alice、Ayaya、Karen、Shinobu、Yoko 五人又能繼續愉快地玩耍啦!數組

「噢……!不是有放上天的煙花嘛!」Karen 興奮地喊道。spa

「啊等等……」Yoko 驚呼。Karen 手持點燃引信的煙花,「嗯??」code

Yoko 最但願見到的是排列優美的煙火,固然不會放過這個機會…… 不過期間彷佛已經很少了。get

nn 個煙火排成一排,從左到右高度分別爲 $h_1,h_2,\cdots$,這些高度兩兩不一樣。it

每次 Yoko 能夠選擇兩個相鄰的煙火交換,這樣的交換能夠進行任意屢次。class

每次 Yoko 還能夠選擇兩個不相鄰的煙火交換,但這樣的交換至多進行一次。im

你的任務是幫助 Yoko 用最少次數的交換,使這些煙火從左到右的高度遞增。

$\color{#0066ff}{輸入格式}$

第一行包含一個正整數 $n$。

第二行包含 $n$ 個正整數 $h_1,h_2,\cdots$,相鄰整數之間用一個空格隔開。

$\color{#0066ff}{輸出格式}$

輸出一個整數,表示最少的交換次數。

$\color{#0066ff}{輸入樣例}$

5
3 5 4 1 2

$\color{#0066ff}{輸出樣例}$

5

$\color{#0066ff}{數據範圍與提示}$

一開始,$5$ 個煙火的高度依次爲 $3,5,4,1,2$。

第 $1$ 次,交換第 $4$ 根菸火和第 $5$ 根菸火,交換後煙火的高度依次爲 $3,5,4,2,1$。

第 $2$ 次,交換第 $3$ 根菸火和第 $4$ 根菸火,交換後煙火的高度依次爲 $3,5,2,4,1$。

第 $3$ 次,交換第 $1$ 根菸火和第 $2$ 根菸火,交換後煙火的高度依次爲 $5,3,2,4,1$。

第 $4$ 次,交換第 $2$ 根菸火和第 $3$ 根菸火,交換後煙火的高度依次爲 $5,2,3,4,1$。

第 $5$ 次,交換第 $1$ 根菸火和第 $5$ 根菸火,交換後煙火的高度依次爲 $1,2,3,4,5$。

能夠證實這是交換次數最少的方案。

$\color{#0066ff}{題解}$

考慮沒有交換任意兩個數一次的操做,那麼答案就是逆序對數

如今咱們有一個交換任意兩個數的操做

咱們確定是要讓此次操做的貢獻最大的

也就是說,減小的逆序對數最多

那麼咱們選的兩個數,左面那個確定是越靠左且越大爲優,右面那個越靠右越小越優

也就是說,咱們可能被交換的數就是全部前綴max取得的點和全部後綴min取得的點

而後咱們處理出了這兩個可能修改的位置數組,都是單調的

咱們如今要在兩個數組內分別選一個數,交換這兩個數所對應的位置,使得對答案的貢獻最大

對於一對$[l,r]$,交換它倆的貢獻是$[l,r]之間權值在[a[r],a[l]]之間的值的個數$

這就是二維數點了, 主席樹能夠維護

如今考慮怎麼快速求出每一個點的貢獻

也就是說,對於每一個可能的右端點,選一個最優的左端點

咱們假設1選擇y最優

那麼能夠得出$G+H>F+I$

而後對於2,由於$G+H+J>F$顯然,因此y確定比x有,也就是說最優勢單調!!

因而咱們就能夠總體二分$O(nlog^2n)$的求出右面每一個點的貢獻,而後取max,交換最優的l和r

最後樹狀數組求一下序列逆序對便可

#include<bits/stdc++.h>
#define LL long long
LL in() {
	char ch; LL x = 0, f = 1;
	while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
	return x * f;
}
const int maxn = 3e5 + 10;
struct Tree {
protected:
	int st[maxn];
	int low(int x) { return x & (-x); }
	int siz;
public:
	void resize(int n) { siz = n; }
	void add(int pos) { while(pos <= siz) st[pos]++, pos += low(pos); }
	int query(int pos) { int re = 0; while(pos) re += st[pos], pos -= low(pos); return re; }
}s;
struct node {
	node *ch[2];
	int num;
	node(int num = 0): num(num) { ch[0] = ch[1] = NULL; }
};
int n, a[maxn];
node *root[maxn];
int st1[maxn], st2[maxn], top1, top2, to[maxn], ans[maxn];
LL tot;
void init() {
	root[0] = new node();
	root[0]->ch[0] = root[0]->ch[1] = root[0];
}
void add(node *&o, node *lst, int l, int r, int p) {
	o = new node(), *o = *lst, o->num++;
	if(l == r) return;
	int mid = (l + r) >> 1;
	if(p <= mid) add(o->ch[0], lst->ch[0], l, mid, p);
	else add(o->ch[1], lst->ch[1], mid + 1, r, p);
}
int getnum(node *x, node *y, int l, int r, int ql, int qr) {
	if(x->num == y->num) return 0;
	if(ql <= l && r <= qr) return y->num - x->num;
	int mid = (l + r) >> 1;
	int cnt = 0;
	if(ql <= mid) cnt += getnum(x->ch[0], y->ch[0], l, mid, ql, qr);
	if(qr > mid) cnt += getnum(x->ch[1], y->ch[1], mid + 1, r, ql, qr);
	return cnt;
}
int getans(int l, int r) {
	if(l >= r) return 0;
	if(a[l] < a[r]) return 0;
	return getnum(root[l - 1], root[r], 1, n, a[r] + 1, a[l] - 1);
}
void cdq(int l, int r, int a, int b) {
	if(a > b) return;
	if(l > r) return;
	if(l == r) {
		for(int i = a; i <= b; i++) {
			int now = getans(st1[l], st2[i]);
			if(now > ans[i]) ans[i] = now, to[i] = l;
		}
		return;
	}
	int mid = (a + b) >> 1;
	//printf("now is work[%d, %d, %d, %d]\n", l, r, a, b);
	ans[mid] = 0;
	for(int i = l; i <= r; i++) {
		int now = getans(st1[i], st2[mid]);
		if(now >= ans[mid]) ans[mid] = now, to[mid] = i;
	}
	cdq(l, to[mid], a, mid - 1);
	cdq(to[mid], r, mid + 1, b);
}
void predoit() {
	s.resize(n);
	init();
	for(int i = 1; i <= n; i++) add(root[i], root[i - 1], 1, n, a[i] = in());
	//st1 can be left
	//st2 can be right
	for(int i = 1; i <= n; i++) {
		while(top2 && a[i] < a[st2[top2]]) top2--;
		if(a[i] > a[st1[top1]]) st1[++top1] = i;
		st2[++top2] = i;
	}
	int max = 0, l = 0, r = 0;
	cdq(1, top1, 1, top2);
	for(int i = 1; i <= top2; i++)
		if(max < ans[i]) max = ans[i], l = st1[to[i]], r = st2[i];
	std::swap(a[l], a[r]);
	if(l ^ r) tot++;
}
void getans() {
	for(int i = n; i >= 1; i--) {
		tot += s.query(a[i]);
		s.add(a[i]);
	}
	printf("%lld\n", tot);
}
int main() {
	n = in();
	predoit();
	getans();
	return 0;
}
相關文章
相關標籤/搜索