【题解】胖胖吃糖果
简化题意
给定 \(n\) 个正整数,要求从中选出若干个,在满足所选任意两数位置差 \(\le m\) 的前提下所选数之和最小,输出最小和。
DP
显然状态是线性的。
题目对所选数的位置有限制,因此设 \(f[i]\) 为选第 \(i\) 个数的前提下前 \(i\) 个数满足题意的最小和。
初值:\(f[0] = 0\);
转移:\(f[i] = min(f[j]+a[i]),j\in[i-m,i)\);
答案:\(min(f[i]),i\in[n-m+1,n]\)。
这样就得到了 \(O(NM)\) 的算法。
Code
// 70pts
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 1;
int n,m,a[N];
int f[N],ans;
int main() {
ios::sync_with_stdio(false);cin.tie(0);
cin>>n>>m;
for(int i = 1; i <= n; ++i) cin>>a[i];
memset(f, 0x3f, sizeof f);
f[0] = 0;
for(int i = 1; i <= n; ++i)
for(int j = max(0,i-m); j < i; ++j)
f[i] = min(f[i], f[j]+a[i]);
ans = 0x3f3f3f3f;
for(int i = n-m+1; i <= n; ++i) ans = min(ans, f[i]);
cout<<ans;
return 0;
}
单调队列优化
因为 \(i\) 和 \(i+1\) 待选决策集合的重叠部分\([i-m+1,i)\),所以如果 \(j<k且f[j]>f[k]\)(即包含 \(j\) 的待选决策集合一定包含 \(k\),且 \(j\) 比 \(k\) 劣),那么 \(f[j]\) 就是无用决策,可以排除。
观察状态转移方程,可以将 \(f[i] = min(f[j]+a[i]),j\in[i-m,i)\) 拆成分别只与 \(i\) 或只与 \(j\) 有关的两部分,即 \(min(f[j]),j\in[i-m,i)\) 和 \(a[i]\),显然是单调队列优化dp的模型。用单增队列维护 \(f[j]\) 即可 \(O(1)\) 转移,总时间复杂度 \(O(N)\)。
AC Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 1;
int n,m,a[N];
int f[N],ans;
int q[N],fro=1,rea;
int main() {
ios::sync_with_stdio(false);cin.tie(0);
cin>>n>>m;
for(int i = 1; i <= n; ++i) cin>>a[i];
ans = 0x3f3f3f3f;
q[++rea] = 0; // 入队 f[0]
for(int i = 1; i <= n; ++i) {
while( fro <= rea && q[fro]+m < i ) ++fro; // 排除过时决策
f[i] = f[q[fro]] + a[i]; // 转移
while( fro <= rea && f[i] <= f[q[rea]] ) --rea; // 维护单调性
q[++rea] = i; // 入队 f[i]
if( i+m-1 >= n ) ans = min(ans, f[i]); // 更新答案
}
cout<<ans;
return 0;
}