比赛刚开始看题面时,感觉这题挺熟悉的 cf某场组队 取一段长度数组元素但要减去长度带来的代价 dp 和牛客小白月赛类似但更简单的贪心
第一感觉先排序 应该可以用贪心的替换法证明每个魔法消耗一段连续的元素是一个最优解 有空再证
d[i]表示以i结尾消耗的最小魔力
d[i]=min{d[j]+a[i]-a[j+1]|k≤j≤i-k}
由于每次至少得选择k个元素施魔法 以k~2k-1结尾的只能一段 初始化为d[i]=a[i]-a[1]
对于i≥2k,枚举以i结尾这一段(也就是最后一次施魔法)的起点(也就是前一次施魔法终点的下一位) 由于有长度至少得为k的限制
d[i+1]=d[i-k+1]+a[i+1]-a[i-k+2]
d[i]=d[i-k]+a[i]-a[i-k+1] d[i+1]=d[i-k]+a[i+1]-a[i-k+1]
d[i]=d[i-k-1]+a[i]-a[i-k] d[i+1]=d[i-k-1]+a[i+1]-a[i-k]
...... .......
d[i]=d[k+1]+a[i]-a[k+2] d[i+1]=d[k+1]+a[i+1]-a[k+2]
d[i]=d[k]+a[i]-a[k+1] d[i+1]=d[k]+a[i+1]-a[k+1]
写出相邻两项i和i+1的状态转移发现
从列(纵向)来看,即单独看i或i+1的状态转移 每一行都有a[i] 说明a[i]对d[i]无影响 可以把a[i]放在后面或min外面 可以把a[i]提出来放在min外面 而剩余两项d[j]-a[j+1] 两者下标差一
其实从实际意义上看d[j]为之前施魔法的魔力消耗总和 j是前一次施魔法的终点 而最后一次施魔法的魔力消耗是a[i]-a[j+1] 显然以前一次施魔法终点的下一位为起点
从行(横向)来看,比较i和i+1的状态转移 i+1只比i多了一行状态转移,且其他行的状态转移除去a[i]与a[i+1]的不同均一致
故可以通过移项将状态转移方程变为d[i]=min{d[j]-a[j+1]|k≤j≤i-k}+a[i] 每次只需O(1)维护一个d[j]-a[j+1]的最小值
然后就是感觉std的代码实现可以学习一下
#includeusing namespace std; const int N=3e5+5; int a[N],d[N]; int main(){ int n,k; cin>>n>>k; for(int i=1;i<=n;i++)cin>>a[i]; sort(a+1,a+1+n); for(int i=k;i 2*k,n+1);i++)d[i]=a[i]-a[1]; if(n<2*k)cout<<d[n]; else{ int mi=d[k]-a[k+1]; for(int i=2*k;i<=n;i++){ d[i]=mi+a[i]; mi=min(mi,d[i-k+1]-a[i-k+2]); } cout< endl; } }
std
#includeusing namespace std; const int N = 3e5 + 7; int dp[N], pre, a[N], n, k; int main() { scanf("%d%d", &n, &k); for (int i = 1; i <= n; ++i) scanf("%d", a + i); sort(a + 1, a + 1 + n); pre = -a[1]; for (int i = 1; i < k; ++i) dp[i] = 2e9; for (int i = k; i <= n; ++i) { dp[i] = pre + a[i]; pre = min(pre, dp[i - k + 1] - a[i - k + 2]); } cout << dp[n]; return 0; }