Codeforces Round #466 (Div. 2) E. Cashback

Codeforces Round #466 (Div. 2) E. Cashback(dp + 贪心)

题意:

给一个长度为\(n\)的序列\(a_i\),给出一个整数\(c\)
定义序列中一段长度为k的区间的贡献为区间和减去前\(\lfloor \frac{k}{c} \rfloor\)小数的和
现在要给序列\(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;
}


posted @ 2018-03-05 18:10  jiachinzhao  阅读(162)  评论(0编辑  收藏  举报