一个就像贪心一样的 DP
思路还是挺巧妙的
首先由于可以任意分段 对于任何一个长度 (len<k) 的段 这一段是不能够去掉任何一个数的 也就是每一个数都要被统计到答案里 所以要是弄出来这样一个分段就把它拆成 (len) 个长度为 (1) 的段是等价的
拆了处理更方便
其次对于长度 (lengeq k) 的段,这里先看长度不为 (k) 的整数倍的段
我们可以把它分成 一个长度为 (mk)((m) 为满足 (mk<len) 的最大整数)的段和几个长度为 (1) 的段。
其中长度为 (mk) 的段取所有情况中最优情况 其它用长度为1的段填满
与上面同理。
现在我们只剩下长度为 (1) 的段和长度为 (mk) 的段了。
接下来 观察后者,如果把它分成 (m) 个长度为 (k) 的段 结果一定不会更劣。
因为去掉的数不会减小,但可能增大。
所以就分开。
然后这题就变成把一些数任意分成长度为 (1) 和 (k) 的段,其中每个长度为 (k) 的段的贡献(就是和)为这段的和减去它的最小值,长度为 (1) 的段就是加上这个数本身了。
然后可以 dp 了 这里大家应该就都会了
$dp[i] = min(dp[i - 1] + w[i],dp[i - c] + sum(i - c + 1,i) - min{w[i-c+1],...,w[i]}) $
最小值用线段树实现了
code:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define mid ((l + r) >> 1)
#define lson (x << 1)
#define rson ((x << 1) | 1)
using namespace std;
typedef long long LL;
LL a[500005] = {0};
LL val[2000005] = {0};
void build(LL x,LL l,LL r){
if(l == r){ val[x] = a[l]; return; }
build(lson,l,mid);
build(rson,mid + 1,r);
val[x] = min(val[lson],val[rson]);
}
LL query(LL x,LL l,LL r,LL L,LL R){
if(L <= l && r <= R) return val[x];
LL ret = 0x3f3f3f3f3f3f3f3f;
if(L <= mid) ret = min(ret,query(lson,l,mid,L,R));
if(R > mid) ret = min(ret,query(rson,mid + 1,r,L,R));
return ret;
}
LL n,c;
LL dp[500005] = {0};
LL pre[500005] = {0};
int main(){
memset(dp,0x3f,sizeof(dp));
cin >> n >> c;
for(LL i = 1;i <= n;i ++) cin >> a[i];
for(LL i = 1;i <= n;i ++) pre[i] = pre[i - 1] + a[i];
// 前缀和求一段的和
build(1,1,n); // 线段树别忘了build
dp[0] = 0;
for(LL i = 1;i <= n;i ++){
if(i >= c) dp[i] = min(dp[i],dp[i - c] + pre[i] - pre[i - c] - query(1,1,n,i - c + 1,i));
dp[i] = min(dp[i],dp[i - 1] + a[i]);
// dp转移
}
cout << dp[n] << endl;
return 0;
}
所以样例解释不能信啊