E. Cashback
题意:
给出常数 c ,定义长度为 n 的序列的价值为:舍去前 (floor)( n/c ) 个最小的数,余下的数的和。
n 个数的序列,要你把它们分隔成多个序列,顺序不能改变。最后的答案为 所有序列的价值的和,求最小的答案。
tags:
我们可以推测出:最后最优的答案,序列长度一定是 c 或 1 。因为如果有 k*c 长度的序列,我们把它分成 k 个长度为 c 的最优; 如果有 k*c+m 的长度序列,那么最后分出 m 个长度为 1 的序列也是更优。
所以就直接 dp 递推: dp[i] 表示前 i 个数可能的最小的价值和。
dp[i] = min(dp[i-1]+a[i] , dp[i-c]+sum[i]-sum[i-c]+get_min(i-c+1, i) ) 。
// 940 E
#include<bits/stdc++.h>
using namespace std;
#pragma comment(linker, "/STACK:102400000,102400000")
#define rep(i,a,b) for (int i=a; i<=b; ++i)
#define per(i,b,a) for (int i=b; i>=a; --i)
#define mes(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define MP make_pair
#define PB push_back
#define fi first
#define se second
#define mid (l+(r-l)/2)
typedef long long ll;
const int N = 200005;
int n, C;
ll tr[N<<2], sum[N], a[N], dp[N];
void build(int ro, int l, int r)
{
if(l==r) { scanf("%lld", &a[l]); tr[ro]=a[l]; return ; }
build(ro<<1, l, mid); build(ro<<1|1, mid+1, r);
tr[ro] = min(tr[ro<<1], tr[ro<<1|1]);
}
ll query(int ro, int l, int r, int ql, int qr)
{
if(ql<=l && r<=qr) return tr[ro];
ll ret = 1e18;
if(ql<=mid) ret = min(ret, query(ro<<1, l, mid, ql, qr));
if(mid<qr) ret = min(ret, query(ro<<1|1, mid+1, r, ql, qr));
return ret;
}
int main()
{
scanf("%d%d", &n, &C);
build(1, 1, n);
rep(i,1,n) sum[i]=sum[i-1]+a[i];
rep(i,1,n)
{
dp[i] = dp[i-1]+a[i];
if(i-C>=0) dp[i] = min(dp[i], dp[i-C]+sum[i]-sum[i-C]-query(1,1,n,i-C+1,i));
}
printf("%lld\n", dp[n]);
return 0;
}