[BalticOI2004]Sequence 數字序列

題目

傳送門 to luogu

題目概要
對於序列 a0,a1,a2,,an1a_0,a_1,a_2,\dots,a_{n-1} ,求序列 b0,b1,b2,,bn1b_0,b_1,b_2,\dots,b_{n-1} ,滿足 i[0,n),biZ\forall i\in[0,n),b_i\in\Zi[1,n),bi1<bi\forall i\in[1,n),b_{i-1}<b_i ,最小化 i=0n1aibi\sum_{i=0}^{n-1}|a_i-b_i|

數據範圍與約定
n106,0a2×109n\le 10^6,0\le a\le 2\times 10^9

基礎設置

語言設置

最優解並非唯一。下文中,如果說某解爲最優解,意思是某解是最優解之一

一個序列用 p0,p1,p2,,pk\langle p_0,p_1,p_2,\dots,p_{k}\rangle 表示。或簡寫成 p\langle p\rangle 。未註明長度,則請聯繫上下文猜測。

M(a0,a1,a2,,an1)M(a_0,a_1,a_2,\dots,a_{n-1})a0,a1,a2,,an1\langle a_0,a_1,a_2,\dots,a_{n-1}\rangle 的中位數。

中位數

一個單調不降序列 a1,a2,a3,,an\langle a_1,a_2,a_3,\dots,a_n\rangle 的中位數是 an+12a_{\lfloor\frac{n+1}{2}\rfloor} 。任意一個序列的中位數,就是將其單調不降排序後的序列的中位數。

或,若下標從零開始,a0,a1,a2,an1\langle a_0,a_1,a_2\dots,a_{n-1}\rangle 的中位數是 an12a_{\lfloor\frac{n-1}{2}\rfloor}

思路

基本情況

首先,將 ai,bia_i,b_i 同時減去 ii ,顯然答案不變,但此時 bb 變成了不降,而非嚴格遞增。

結論零

  • 如果序列 a0,a1,a2,,an1\langle a_0,a_1,a_2,\dots,a_{n-1}\rangle 的中位數是 ata_t ,那麼去掉其中任意一個數,中位數變成 at1,at,at+1a_{t-1},a_t,a_{t+1} 其中一個。

似乎無需證明?因爲太顯然了。但是這個結論很有用。

結論一

  • 假若最優解是 x,x,x,,x\langle x,x,x,\dots,x\rangle ,那麼 xx 可以取 a\langle a\rangle 的中位數。

這是顯然的。根據絕對值的幾何意義可知。據說是初一的內容?膜一下小學選手 orz\text{orz}

當然了,我們也可以有一個推論:

  • 如果 xx 是中位數,在 xy1y2x\le y_1\le y_2y2y1xy_2\le y_1\le x 時,y1,y1,y1,,y1\langle y_1,y_1,y_1,\dots,y_1\rangle 不劣於 y2,y2,y2,,y2\langle y_2,y_2,y_2,\dots,y_2\rangle

大白話的同義轉述:靠近中位數就強!——但是僅限於 x,x,x,,x\langle x,x,x,\dots,x\rangle 這樣全部相同的。

我們還可以多推一點:

  • 最優解是 x,x,x,,x\langle x,x,x,\dots,x\rangle ,等價於 i[0,n),M(a0,a1,a2,,ai1)M(ai,ai+1,ai+2,,an1)\forall i\in[0,n),M(a_0,a_1,a_2,\dots,a_{i-1})\ge M(a_i,a_{i+1},a_{i+2},\dots,a_{n-1})

假設不是這樣的,就可以分別考慮這兩個子區間的最優解,至少存在一個 M(L),,M(L),M(R),,M(R)\langle M(L),\dots,M(L),M(R),\dots,M(R)\rangle 的解。這裏的 L,RL,R 代表這兩個子序列。

同時,它也是充分的。如果總是如此,就沒有辦法將它拆分了。因爲在任意拆分下,M(L),M(R)M(L),M(R) 總有一個不超過原序列的中位數,而另一個就會不小於原序列的中位數。

給一張圖片。每一條橫線代表 bb 取值相同的部分。顯然這個 bb 會是覆蓋部分的 aa 的中位數。

在這裏插入圖片描述如果最優解是黑色部分所示那般,任選一個空隙切開,圖中是綠色的分割線,左右兩邊的中位數用紅色表示。左邊部分的中位數小於綠線左側的區間的中位數,而右邊部分是大於的(見圖中紅色的VV )。紅線的大小關係已然清晰!

類似地,這也是充分的。如果紅線都已經是左邊較大,無論怎麼拆也拼不回去了。

我感覺這裏有一些抽象?不妨將整篇博客看完,回頭重新看此處。

結論二

  • 設最優解是 x,x,x,,x\langle x,x,x,\dots,x\rangle 。對於另外一個方案 b0,b1,b2,,bn1\langle b_0,b_1,b_2,\dots,b_{n-1}\rangle ,在 b0xb_0\ge x 時,一定不如 b0,b0,b0,,b0\langle b_0,b_0,b_0,\dots,b_0\rangle ,至少不會更好。

首先要注意到,這樣的 b\langle b\rangle 滿足 i[0,n),bix\forall i\in[0,n),b_i\ge x 。畢竟是不降的。

數學歸納法證明。在 n=1n=1 的時候,顯然如此,因爲二者是同一個方案。

n>1n>1 時,假設 最後 n1n-1 個元素的最優解是 y,y,y,y\langle y,y,y\dots,y\rangle 。顯然 yxy\le x ,否則全局最優解爲 x,y,y,y,,y\langle x,y,y,y,\dots,y\rangle 。因而有 yxb1y\le x\le b_1 ,根據歸納法,b0,b1,b1,b1,,b1\langle b_0,b_1,b_1,b_1,\dots,b_1\rangle 是一個更優的方案。

然而,yb0b1y\le b_0\le b_1 ,所以 b0,b0,b0,,b0\langle b_0,b_0,b_0,\dots,b_0\rangle 甚至更優(後 n1n-1 個數更靠近中位數)。

類似地,我們可以說:

  • bn1xb_{n-1}\le x 時,不如 bn1,bn1,bn1,,bn1\langle b_{n-1},b_{n-1},b_{n-1},\dots,b_{n-1}\rangle

遺留問題——最後面(最前面)n1n-1 個數是否存在一個全部相等的最優解?

當然如此。因爲 [1,i)[1,i) 的中位數就是 [0,i)[0,i) 去頭。根據 結論零 ,中位數最多變小一個數,並且這隻在原來的長度爲奇數時成立。不妨在數軸上畫出來,更形象。

在這裏插入圖片描述中位數原本是黑色的數,去掉一個數,可能變成了紅色的那個數。大區間運用 結論一 的最後一個推論,右邊的區間的中位數是綠色的那個,要小於黑色的。反證法,假設 [1,n)[1,n) 不滿足條件,那麼紅色的數就要小於綠色的數。

但是,仔細看圖——其實綠色也是 [1,i)[1,i) 的中位數!因爲目前的數量是偶數,最中間的兩個數之間,都算作中位數。所以即使如此,[1,i)[1,i) 的中位數還是不小於 [i,n)[i,n) 的中位數,滿足條件!

結論三

  • a0,a1,a2,,ak1\langle a_0,a_1,a_2,\dots,a_{k-1}\rangle 的最優解是 x,x,x,,x\langle x,x,x,\dots,x\rangle ,而 ak,ak+1,ak+2,,an1\langle a_k,a_{k+1},a_{k+2},\dots,a_{n-1}\rangle 的最優解是 y,y,y,,y\langle y,y,y,\dots,y\rangle ,倘若 x>yx>y ,那麼整個區間的最優解 b\langle b\rangle 滿足 bk1xb_{k-1}\le xybky\le b_k

反證法。假設 bk1xb_{k-1}\ge x ,就可以推出 bkbk1xb_k\ge b_{k-1}\ge x ——這意味着 b0=b1=b2==bk1=xbkb_0=b_1=b_2=\dots=b_{k-1}=x\le b_k 是被允許的,就會得到更優的解。

ybky\le b_k 類似,可以讓右側取到更優的解。

結論四

  • 我們可以合併答案了!

結論三 告訴我們 bk1xb_{k-1}\le x ,但 結論二 告訴我們,這樣的情況,不如 bk1,bk1,bk1,,bk1\langle b_{k-1},b_{k-1},b_{k-1},\dots,b_{k-1}\rangle

所以,其中一個最優解是 p,p,p,,p,q,q,q,q\langle p,p,p,\dots,p,q,q,q\dots,q\rangle ,其中 p=bk1,q=bkp=b_{k-1},q=b_k

注意到 pxp\le x ,所以 pp 越大越好(接近中位數);類似地,qq 越小越好。所以,平衡點是 p=qp=q 。於是乎,整個區間的最優解是一個全部相同的數字。根據 結論一 ,這個數字是中位數!

寫出這個結論,就是:

  • 每個數字是一個區間。若兩個相鄰區間的中位數是逆序,那麼將二者合併爲一個區間。對於每一個區間,取 x,x,x,,x\langle x,x,x,\dots,x\rangle 作爲解,xx 是中位數。

代碼

目標

維護很多個區間,及每個區間的中位數。每次在末尾加入一個新區間,並可能將相鄰的區間進行合併。

顯然,我們有 O(n)\mathcal O(n) 次合併、O(n)\mathcal O(n) 次查詢。需要一種數據結構吧?

普蘭AA

使用平衡樹,中位數就是第 size+12\lfloor\frac{size+1}{2}\rfloor 大的數字。複雜度 O(nlog2n)\mathcal O(n\log^2n) ,慢在啓發式合併上。

特性

我們尋找一下有什麼特性。衆所周知,這是一個很難的問題:

  • 合併兩個可重集。
  • 輸出一個可重集的中位數。

反正我只會用平衡樹QAQQAQ ,我好弱……

但是在這道題裏,我們有這樣的特性:當前可重集的中位數大於你,與另一個集合合併後,中位數小於你了

畫一張圖:

在這裏插入圖片描述
最重要的是綠色寫出的:原中位數和新中位數之間沒有別人

假設它需要和前一個區間繼續合併,那麼前一個區間的中位數就在綠色部分中。

合併後的中位數,肯定也是在綠色部分中的。更具體些,一定在紅色之上、前一個區間的中位數之下。

所以,我們放心大膽地說,新區間中大於中位數的數,包含了兩個原區間中大於所屬區間的中位數的數。有點繞?記原來的序列是 AABB ,那麼 {xxA,x>M(A)}+{xxB,x>M(b)}{xxA+B,x>M(A+B)}\{x|x\in A,x>M(A)\}+\{x|x\in B,x>M(b)\}\subseteq\{x|x\in A+B,x>M(A+B)\}

爲啥?我不是已經給了圖片嗎?新區間的中位數在綠色部分,比它大的就是比紅色中位數大的。上一個區間?更顯然,中位數變小了。

問題來了,這兩個合併之後還得接着合併啊?仔細看:當前區間的數字恰好分居上一個區間的中位數兩側!所以,新區間的中位數恰好爲上一個區間的中位數

然後就不會有更多的合併了。只需要執行兩次合併檢查即可!

普蘭BB

用一個大根堆,總是存儲較小的一半元素,這樣堆頂自然是根。我們需要支持的操作變成了:

  • 區間的合併——堆的合併。共 O(n)\mathcal O(n) 次。
  • 區間中位數的查詢——堆頂元素查詢。共 O(n)\mathcal O(n) 次。
  • 區間中位數調整——刪去堆頂元素。共 O(n)\mathcal O(n) 次。

直接使用左偏樹即可,複雜度 O(nlogn)\mathcal O(n\log n) ,可通過。但是我的代碼要開O2

代碼

兩段以 /**/ 開頭的代碼,使用其中一個即可。第一個是穩妥的方式,第二個是證明我所說非假。

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
inline int readint() {
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0' or c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c and c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
void writeint(int x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar((x%10)^48);
}
# define MB template < typename T >
MB void getMax(T &a,const T &b){ if(a < b) a = b; }
MB void getMin(T &a,const T &b){ if(b < a) a = b; }
int ABS(int x){ return x < 0 ? -x : x; }
# define FOR(i,n) for(int i=0; i<(n); ++i)
typedef long long int_;

template < class T, class CMP = less<T> >
class ZPS{ // 左偏樹(默認大根,類似優先隊列)
protected:
	CMP comparer; unsigned siz;
	struct Node{
		T data; int dist; Node* son[2];
		Node(T val):data(val),dist(1){
			son[0] = son[1] = NULL;
		}
	} *root;
	Node* merge(Node* a,Node* b){
		if(a == NULL) return b;
		if(b == NULL) return a;
		if(comparer(a->data,b->data)) swap(a,b);
		a->son[1] = merge(a->son[1],b);
		int ld = 0; if(a->son[0] != NULL)
			ld = a->son[0]->dist;
		int rd = a->son[1]->dist;
		if(ld < rd) swap(a->son[0],a->son[1]);
		a->dist = min(ld,rd)+1; return a;
	}
public:
	ZPS(){ root = NULL, siz = 0; }
	void push(T val){
		root = merge(root,new Node(val));
		++ siz; // 人工統計,大大的好!
	}
	T top() const { return root->data; }
	unsigned size() const { return siz; }
	void pop(){
		if(root == NULL) return ;
		-- siz; // manually count
		Node* p = root->son[0];
		p = merge(p,root->son[1]);
		delete root; root = p;
	}
	void operator += (ZPS<T,CMP> &that){
		siz += that.siz; // 小小的好!
		root = merge(root,that.root);
	}
};

ZPS<int> a[1000000];
int b[1000000];
vector<int> sta;
vector<int> way;
int main(){
	int n = readint();
	for(int i=0; i<n; ++i){
		b[i] = readint()-i; // 改寫成 <=
		a[i].push(b[i]); int now = i;

		/**/while(not sta.empty()){
		/**/	int last = sta.back();
		/**/	if(a[last].top() <= a[now].top())
		/**/		break; // valid
		/**/	a[last] += a[now]; // union
		/**/	if(a[last].size() > (i-last+2u)/2)
		/**/		a[last].pop();
		/**/	now = last, sta.pop_back();
		/**/}

		/**/for(int ZXY=0; ZXY<2; ++ZXY)
		/**/	if(not sta.empty()){
		/**/		int last = sta.back();
		/**/		if(a[last].top() > a[now].top()){
		/**/			sta.pop_back();
		/**/			a[last] += a[now];
		/**/			if(a[last].size() > (i-last+2u)/2)
		/**/				a[last].pop();
		/**/			now = last;
		/**/		}
		/**/	}

		sta.push_back(now);
	}
	int_ ans = 0;
	for(int i=n-1,zxy; ~i; --i){
		if(sta.back() > i)
			sta.pop_back();
		zxy = a[sta.back()].top();
		ans += ABS(zxy-b[i]);
		way.push_back(zxy+i);
	}
	printf("%lld\n",ans);
	while(not way.empty()){
		writeint(way.back());
		putchar(' ');
		way.pop_back();
	}
	return 0;
}

後記

別的做法

傳送門 to 百度文庫,我看不懂。😦

以及,思路顯然、優化困難,動態規劃的方法。傳送門 to 洛谷題解,我也看不懂。😭

我の神明

在代碼中多多使用 zxy ,尤其是模數——多打 %zxy ,經實驗,有效增加 ACAC 機率!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章