Codeforces Round #466 (Div. 2) E. Cashback(dp + 贪心)
题意:
给一个长度为(n)的序列(a_i),给出一个整数(c)
定义序列中一段长度为k的区间的贡献为区间和减去前(lfloor frac{k}{c}
floor)小数的和
现在要给序列(a_i)做一个划分,使得贡献的总和最小。
思路:
容易想到最裸的dp的思路
(dp[i] = min(dp[j] + cost(j,i)) , j < i), (cost(j,i))就是区间[j,i]的贡献
这样暴力枚举复杂度是(O(n ^ {2} log n))的
观察发现 划分成一段(k * c)的区间不会优于划分成(k)段长度为(c)的区间
同理分成非(c)的整数倍长度的区间是不会优于拆分成(c)的整数倍和一段长度小于(c)的区间
由于长度小于(c)的区间是没有贡献的,所以和拆成长度为1的区间是等价的。
所以最优的划分方式就是分成长度为1或者分成长度为c,再来做dp就可以了。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 1e5 + 10;
const int inf = 0x3f3f3f3f;
int a[N];
int n, c;
int mi[N << 2];
LL dp[N],sum[N];
void build(int l,int r,int rt){
if(l == r){
scanf("%d",a + l);
mi[rt] = a[l];
return ;
}
int m = l + r >> 1;
build(l,m,rt<<1);
build(m+1,r,rt<<1|1);
mi[rt] = min(mi[rt<<1],mi[rt<<1|1]);
}
int querymi(int L,int R,int l,int r,int rt){
if(L <= l && R >= r) return mi[rt];
int ans = inf;
int m = l + r >> 1;
if(L <= m) ans = min(ans, querymi(L,R,l,m,rt<<1));
if(R > m) ans = min(ans,querymi(L,R,m+1,r,rt<<1|1));
return ans;
}
int main(){
scanf("%d%d",&n,&c);
build(1,n,1);
for(int i = 1;i <= n;i++) sum[i] = sum[i - 1] + a[i];
for(int i = 1;i <= n;i++){
dp[i] = dp[i - 1] + a[i];
if(i >= c){
dp[i] = min(dp[i], dp[i - c] + sum[i] - sum[i - c] - querymi(i - c + 1,i,1,n,1));
}
}
cout<<dp[n]<<endl;
return 0;
}