Codeforces 13C Sequence

https://codeforces.com/problemset/problem/13/C

题目

给一个序列,你可以把每个元素+1或-1,问把这个序列变成不下降的序列最少需要多少次操作。(5000个元素)

题解

有一个结论是操作次数最小形成的序列中,包含“所有元素都是原来序列出现过的元素的序列”。

有个构造的证明= =

设原来的序列为A,构造的序列为S,目标是$sum leftlvert A[i]-S[i] ight vert$最小

对A的前n个元素进行构造,设构造出来的序列是S(n),并且S(n)满足操作次数最小,S(n)中的元素都来自A,那么

$n=1$时,显然成立,操作次数为0

$n>1$时,设满足条件的$S(n-1)$存在,

1.如果$A[n]>S(n-1)[最后一个元素]$,那么$S(n)=S(n-1)cdot A[n]$,这样新增的操作数为0,原来就是最小的,于是新的序列也是最小的

2.如果$A[n]leqslant S(n-1)[最后一个元素]$,那么,$S(n)[最后一个元素]$就不能比$S(n-1)[最后一个元素]$大,否则不会更优。那么和之前的一起下降

下降到什么程度呢?可以得到如果下降到S[l~n]几个数是同一个值,那么它们的值是A[l~n]的中位数,这样这几个数的操作数最小,而其他的依然可以继续选择中位数进行下降,这样S(n)也存在,是$S(l-1)cdot mid(A[lsim n])^{n-l}$

那么可以直接这样来构造答案,

$dp[i]=min_{j<i}left(dp[j]+mid(A[jsim n]) ight)$,然后就需要提前算区间中位数了……$O(n^3)$

由于包含中位数的原序列离散化后值域比较小,直接枚举原序列,可以避免求中位数

$dp[i][j]$表示前i个元素,最后一个元素是j(枚举原序列)

那么,$dp[i][j]=min_{kleqslant j}left(dp[i-1][k]+leftlvert A[i]-P[j] ight vert ight)$,其中$min_{kleqslant j}left(dp[i-1][k] ight)$在之前已经循环过,所以可以O(1)转移,状态数O(n^2),由于内存限制,需要滚动优化

另外还有其他的优化= =等以后学了再看

AC代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define REP(i,a,b) for(register int i=(a); i<(b); i++)
#define REPE(i,a,b) for(register int i=(a); i<=(b); i++)
#define PERE(i,a,b) for(register int i=(a); i>=(b); i--)
using namespace std;
typedef long long ll;
#define MAXN 5007
int n, nn;
int a[MAXN], b[MAXN];
ll dp[MAXN];
int main() {
	scanf("%d", &n);
	REP(i,0,n) scanf("%d", &a[i]);
	memcpy(b, a, n*sizeof(int));
	sort(b,b+n);
	nn = unique(b,b+n)-b;
	memset(dp,0,sizeof(dp));
	REP(i,0,n) {
		ll mdp = dp[0];
		REPE(j,0,nn) {
			mdp = min(mdp, dp[j]);
			dp[j] = mdp+abs(a[i]-b[j]);
		}
	}
	ll ans=0x7f7f7f7f7f7f7f7f;
	REP(j,0,nn) ans = min(ans, dp[j]);
	printf("%lld
", ans);
	return 0;
}
原文地址:https://www.cnblogs.com/sahdsg/p/12390341.html