[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 机率!

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