CF940E Cashback

题目描述

把一个序列分成若干段,若一段的长度为 \(k\),则把那段中前 \(\lfloor\frac{k}{c}\rfloor\) 小的数删去, \(c\) 为给定常数,最小化每段和。

解法

显然每段越短越好,因为如果把一个段增大的话,段内的最小值可能减小,而删去的数的总数也可能减少。还有一个例子也可以说明,即两个小区间分别的最小值之和显然比这两个区间的并的大区间的前两个最小值之和大,从而使得最后的答案更小。

而对于长度小于 \(c\) 的区间对答案是没有正向贡献的,于是每次取长度为 \(c\) 的一段是最优的,那么有

\[dp_i=max\{dp_{i-1},dp_{i-c}+min\{a_j|\ j\in(i-c,i]\ \}\}\quad i\in[c,n] \]

\(dp_i\) 表示把前 \(i\) 个数分成若干段,每段的最小值之和的最大值。那么方程的两项分别对应断开区间和不断。

\(i\) 的值是连续的,后面那一堆显然可以用单调队列优化掉,那么最后的复杂度就是 \(\Theta(n)\)

#include<stdio.h>
#define N 100007
#define ll long long

template<class T>
inline void read(T &x){
	x=0; bool flag=0; char c=getchar();
	while(c<'0'||c>'9'){if(c=='-') flag=1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
	x=flag? -x:x;
}

inline ll max(ll x,ll y){return x>y? x:y;}

int n,c,Q[N];
ll sum=0,a[N],dp[N];
int main(){
	read(n),read(c);
	for(int i=1;i<=n;i++)
		read(a[i]),sum+=a[i];
	int l=1,r=0;
	for(int i=1;i<=n;i++){
		while(l<=r&&a[Q[r]]>=a[i]) r--;
		Q[++r]=i;
		while(l<=r&&Q[l]<=i-c) l++;
		if(i>=c) dp[i]=max(dp[i-1],dp[i-c]+a[Q[l]]);
	}
	printf("%lld",sum-dp[n]);
}
posted @ 2020-10-20 19:22  Kreap  阅读(65)  评论(0编辑  收藏  举报