题解
这么明显的一个dp,我怎么就没看出来呢?!
首先我们需要一些前提条件:任何划分出来的一个区间的长度不应该超过c。
如果这个区间长度大于c,那么设len=n∗c+klen=n∗c+k,那么这个区间应该被分成n个长度为c的区间和一个长度为k的区间,因为这样的话去掉的元素数量是相同的,并且区间更小有利于较小的值不易被取到。因为每次去掉的最小值仅仅是局部最小值,不一定是全局最小值。大区间就不一样了,虽然大区间去掉的元素数量相同,但是这些元素会更小一些。
这样的话,我们划分出来的区间应该尽量使得区间的长度为c。
然后我们使用dp来解决剩下的部分:
dp[i]dp[i]表示前i个元素组成的集合对应的答案。
状态转移方程:
策略:第i个是否与前c-1个构成一个长为c的区间。
dp[i]=min(dp[i−1]+a[i],dp[i−c+1]+sum[i]−sum[i−c]+min[i−c+1,i])dp[i]=min(dp[i−1]+a[i],dp[i−c+1]+sum[i]−sum[i−c]+min[i−c+1,i])
由于用到了min[i−c+1,i]min[i−c+1,i]我们还需要维护一个最近cc<script type="math/tex" id="MathJax-Element-339">c</script>个a元素的最小值,方便起见,直接使用multiset即可。
反思
以后遇到这种序列题目,找出一些必要条件之后一定往dp上多想一想。
代码
#include <cstdio>
#include <set>
using namespace std;
typedef long long ll;
const int maxn = 100007;
int a[maxn],n,c;
ll dp[maxn],sum[maxn];
multiset<int> st;
int main(){scanf("%d%d",&n,&c);for(int i = 1;i <= n;++i){scanf("%d",&a[i]);dp[i] = sum[i] = sum[i-1] + a[i];}for(int i = 1;i < c;++i)st.insert(a[i]);for(int i = c;i <= n;++i){st.insert(a[i]);dp[i] = min(dp[i-1] + a[i],dp[i-c] + sum[i] - sum[i-c] - *st.begin());st.erase(st.find(a[i-c+1]));}printf("%lld\n",dp[n]);return 0;
}