Cry_For_theMoon  

传送门

这场真的是 rp++ 场。B、C 题都是一眼搞了个乱搞做法结果是列表首A。D 题虽然没搞出来但也是一眼猜到了贪心的结论,可惜没往下面继续分析。

由于 \(b,c\) 一个上升,一个下降,容易考虑这样一种构造方案:

如果 \(a_i<a_{i+1}\)\(a\) 上升,那么我们就令 \(b_{i+1}=b_i+(a_{i+1}-a_i),c_{i+1}=c_i\). 就是在上升的 \(b\) 中加上这段值;反之,如果 \(a\) 下降,就在下降的 \(c\) 中体现这段值,即令 \(b_{i+1}=b_i,c_{i+1}=c_i+(a_{i}-a_{i+1}).\)

直觉来看,这是很显然的。假设 \(i \rightarrow i+1\)\(a\) 中上升,那么我们不得不在 \(b\) 中至少加上 \((a_{i+1}-a_{i}).\) 如果 \(c\) 更小了,那么 \(b\) 增加的就要更多。由于 \(b\) 单调递增 \(c\) 单调递减,所以代价可能变少的情况(\(b\) 为负数且 \(c\) 为正数)的情况应该比较少。但是我们必须严谨地证明这个结论,否则就没有意义了。

上面的构造方案,可以形式化地归纳为:对于每个 \(1 \le i \lt n\),要么 \(b_i=b_{i+1}\),要么 \(c_{i+1}\).

考虑反证法证明这一点,即存在一个位置 \(1\le k \le n\) ,使得 \(b_{k} < b_{k+1}\)\(c_{k} > c_{k+1}\) 同时存在。

容易发现我们可以对 \(b,c\)\(k\) 前缀一个 \(+1\) 一个 \(-1\) 或者对 \(b,c\)\(k+1\) 后缀一个 \(-1\) 一个 \(+1\) 来实现满足单调性,\(b_i+c_i=a_i\) 的同时调整 \(b_i,b_{i+1}\) 还有 \(c_i,c_{i+1}\) 之间的大小关系。

但我们需要考虑正负以确定 \(b,c\) 每个元素的绝对值之和是否改变。容易发现,当 \(b_k<0\) 的时候,我们执行一次前缀操作,可以令 \(b_i\)\(b_{i+1}\) 更接近一步,同时绝对值之和不会更大(因为 \(b\) 的每个元素绝对值都少了 \(1\). 而 \(c\) 中每个对应元素的绝对值最多只会增加 \(1\).)。如果 \(b_k \ge 0\). 则 \(b_{k+1}>0\),那么我们执行一次后缀操作,同样令 \(b_i\)\(b_{i+1}\) 更接近一步,类似地,绝对值之和也不会变大。所以不断执行前缀 \(+1\) 和后缀 \(-1\) 操作就能令 \(b_i=b_{i+1}\). 同理,当然也能令 \(c_i=c_{i+1}\). 所以这种贪心构造的正确性得到了保证。(P.S. 如果 \(b_i\)\(b_{i+1}\) 的差大于 \(c_i\)\(c_{i+1}\) 的差,你会发现我们无法让 \(b_i=b_{i+1}\). 因为调整的过程中 \(c_k\) 就会小于 \(c_{k+1}\). 但你发现这种时候我们就可以让 \(c_k=c_{k+1}\). 同时不难发现,调整前 \(b_{i+1}-b_i\)\(c_i=c_{i+1}\) 的大小关系,就对应着 \(a_i\)\(a_{i+1}\) 的大小关系。这里各个方向联系紧密,非常巧妙(AT就成天出这种思维题))。

题外话:如果觉得一加一减比较麻烦,最后证明的时候可以学官方题解把 \(c\) 取相反数化成 \(b-c=a\) ,但我觉得一般人在不知道结论证明前是不会想到这种方式的,AT的题解总是干这种事情让我理解很困难。

到这里,我们就得到了贪心构造的方法。但是一个问题是我们还没有确定 \(b_1,c_1\). 当确定了 \(b_1=x\) 后,我们就唯一确定了整个 \(b,c\).

一位六年级学弟赛场上用三分 \(x\) 加上这个贪心构造的方法 AC 了 orz orz orz. 但我们实际上不需要三分,有更严谨的 \(O(n)\) 做法。

首先,我们将 \(b_{i}\) 转移到 \(b_{i+1}\) 所加的量记作 \(\Delta_i\). 类似地,把 \(c_i\) 转移到 \(c_{i+1}\) 所减的量记作 \(\Gamma_i\). 容易发现 \(\Delta\)\(\Gamma\) 都是非负的。容易想到写出 \(b_1=x\) 后整个序列的样子:

\[b=x,x+\Delta_1,x+\Delta_1+\Delta_2,... \]

类似地:

\[c=a_1-x,a_1-x+\Gamma _1,a_1-x+\Gamma_1+\Gamma_2,... \]

那么你发现 \(b,c\) 其实有一部分都是固定的,只不过 \(b\) 的每个元素整体加上 \(x\)\(c\) 的每个元素整体减去 \(x\). 现在的问题是,给定若干个关于 \(x\) 的一次式子,求其最小的绝对值和。这是初一数学里每个人都会学到的知识,也是一个玩烂的 trick:把每个式子写成 \(|x-a|\) 的形式,然后 \(x\) 就是所有 \(a\) 的中位数。

上面的东西显然都可以写成 \(x-a\) 的形式,对于 \(b\) 来说,其可以写成:

\[x-(-\Delta_1-\Delta_2-...) \]

的形式。对于 \(c\) 来说,我们无法进行恒等变形,但考虑相反数的绝对值相等,可以取相反数改写成:

\[x-(a_1-\Gamma_1-\Gamma_2-...) \]

的形式。

那么 \(\Delta\)\(\Gamma\) 容易在 \(O(n)\) 的时间内求出,自然改写后每个 \(|x-a|\)\(a\) 也能在 \(O(n)\) 的时间内算出。最后求 \(2n\) 个元素的中位数,显然也是可以 \(O(n)\) 内分治(或者 nth_element)实现的。当然,直接 sort 也没有任何问题,谁不想偷懒呢。

不用感受函数的凹凸性(雾)。我们就在 \(O(n \log n) / O(n)\)(吊打斜率优化)的复杂度内解决了这个 D.

#include<bits/stdc++.h>
#define rep(i,a,b) for(ll i=(a);i<=(b);i++)
#define per(i,a,b) for(ll i=(a);i>=(b);i--)
#define op(x) ((x&1)?x+1:x-1)
#define odd(x) (x&1)
#define even(x) (!odd(x))
#define lc(x) (x<<1)
#define rc(x) (lc(x)|1)
#define lowbit(x) (x&-x)
#define Max(a,b) (a>b?a:b)
#define Min(a,b) (a<b?a:b)
#define next Cry_For_theMoon
#define il inline
#define pb(x) push_back(x)
#define is(x) insert(x)
#define sit set<int>::iterator
#define mapit map<int,int>::iterator
#define pi pair<int,int>
#define ppi pair<int,pi>
#define pp pair<pi,pi>
#define fr first
#define se second
#define vit vector<int>::iterator
#define mp(x,y) make_pair(x,y)
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
using namespace std;
const int MAXN=4e5+10;
ll n,a[MAXN],db[MAXN],dc[MAXN],b[MAXN],c[MAXN],d[MAXN],ans;
int main(){
	cin>>n;
	rep(i,1,n){
		cin>>a[i];
	}
	rep(i,1,n-1){
		if(a[i]<a[i+1]){
			db[i]=a[i+1]-a[i]; 
		}else{
			dc[i]=a[i]-a[i+1];
		}
	}
	ll sum=0;
	rep(i,1,n){
		d[i]=sum;
		sum-=db[i];		
	}
	sum=a[1];
	rep(i,1,n){
		d[i+n]=sum;
		sum-=dc[i];
	}
	sort(d+1,d+1+2*n);
	b[1]=d[n];
	c[1]=b[1]-a[1];
	rep(i,1,n){
		b[i+1]=b[i]+db[i];
		c[i+1]=c[i]+dc[i];
	}
	rep(i,1,n){
		ans+=abs(b[i]);
		ans+=abs(c[i]);
	}
	cout<<ans<<endl;
	return 0;
}

DP 解法有时间再补(之前写了那么多斜率优化都没想到 DP 解法确实需要好好学习)

posted on 2021-07-19 15:15  Cry_For_theMoon  阅读(231)  评论(5编辑  收藏  举报