codeforces Pictures with Kittens(easy & hard version) - 动态规划

codeforces Pictures with Kittens(easy & hard version) - 动态规划

题干

The only difference between easy and hard versions is the constraints.

Vova likes pictures with kittens. The news feed in the social network he uses can be represented as an array of n consecutive pictures (with kittens, of course). Vova likes all these pictures, but some are more beautiful than the others: the i-th picture has beauty ai.

Vova wants to repost exactly x pictures in such a way that:

each segment of the news feed of at least k consecutive pictures has at least one picture reposted by Vova;
the sum of beauty values of reposted pictures is maximum possible.
For example, if k=1 then Vova has to repost all the pictures in the news feed. If k=2 then Vova can skip some pictures, but between every pair of consecutive pictures Vova has to repost at least one of them.

Your task is to calculate the maximum possible sum of values of reposted pictures if Vova follows conditions described above, or say that there is no way to satisfy all conditions.

Input
The first line of the input contains three integers n,k and x (1≤k,x≤n≤200) — the number of pictures in the news feed, the minimum length of segment with at least one repost in it and the number of pictures Vova is ready to repost.

The second line of the input contains n integers a1,a2,…,an (1≤ai≤109), where ai is the beauty of the i-th picture.

Output
Print -1 if there is no way to repost some pictures to satisfy all the conditions in the problem statement.

Otherwise print one integer — the maximum sum of values of reposted pictures if Vova follows conditions described in the problem statement.

知识点&算法

根本思路是dp,b[i]维护美丽度,dp[i][j]维护在选择b[i]且当前一共选择了j张图片的情况下的最大美丽度。
考虑状态转移方程:
dp[i][j] = max(dp[i][j],dp[ii][j-1] + b[i])
其中ii为i - k 到 i - 1的所有值,因为选择了b[i],所以最多从i-k开始转移,否则就没法满足任意连续k个至少选1的条件了。
最后我们遍历所有dp[n-1 ~ n-k][x],最大者即为答案(b中最后k个元素中一定有元素应当被选中,因此只要遍历最后k个即可,如果不存在答案说明不存在合法策略)
特别要注意的是,初始时dp数组应该置无穷小,即未处理状态(类似背包思想)。

hard和easy的唯一差别就是数据量。dp过程中最好优化的部分就是在状态转移时选择最大前一状态的地方,如果有一个数据结构能够维护区间内的dp数组最大情况,时间复杂度就能大大降低。
一种思路是用内部有序的数据结构,比如set或者单调队列。set的实现很简单,在暴力dp情况下稍加改动即可。
关于单调队列的优化,这里选择用双端队列(内部储存dp元素的值和最后选择元素在b中的下标)实现,dp数组的值每次从前入队,因此双端队列中越靠后的元素在b中的下标就越靠左,因此当双端队列最后的元素下标 < i - k 时,我们就将它出队。
同理,越靠左的元素在b中的下标就越靠右,即如果新入队的元素比队列最左的元素更大,我们大可以将原先队列中的元素抛弃,因为新的元素更大,而且在b中的下标更靠右(更不容易超出k的范围)。

特别要注意的是,未优化时选择个数(背包容量)的遍历顺序是无所谓的,但是优化写法下背包容量的遍历顺序一定要是从小到大,这样的目的是为了在遍历到j时,保证j-1的数据结构还保持着之前的状态。

题解

无优化dp:

hard version会卡在26

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
int n,x,k;
ll b[205],dp[205][205],res = -1e15;

int main()
{
    cin>>n>>k>>x;
    for(int i = 0 ; i < n ; ++i) scanf("%d",b+i);
    //全部置极小,不可选状态   
    for(int i = 0 ; i < n ; ++i){
        for(int j = 0 ; j < n ; ++j) dp[i][j] = -1e15;
    }
    dp[0][1] = b[0];
    for(int i = 1 ; i < n ; ++i){
        //第一个k内可以无脑选一个
        //前=严格按照k组内必选的原则,dp[i][0]不能盲目置0,因为这个状态之前可能存在k组内未选上的情况
        if(i <= k - 1) dp[i][1] = b[i];
        for(int j = 1 ; j <= i + 1 ; ++j){
            for(int ii = max(i - k,0) ; ii < i ; ++ii){
                dp[i][j] = max(dp[i][j],dp[ii][j-1] + b[i]);
            }
        }
    }
    //数组倒数k个元素一定有一个要被选中
    for(int i = n - 1 ; i > n - 1 - k ; --i) {
        res = max(res,dp[i][x]);
    }
    if(res < 1) cout<<-1<<endl;
    else cout<<res<<endl;
    return 0;
}

/*
     给定n位数组a,ai表示美丽度,选x张图片:
        任意k张连续图片中至少得选一张
        图片总美丽度最大
    dp[i][j]表示遍历到下标为i的图片时,已选j张图片的最大美丽度
    dp[i][j] = max(dp[i-k ~ i-1][j-1] + b[i]);
    一开始没过竟然是因为数据开小了
*/

有序数据类型优化,set实现:

hard version会卡在36

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
int n,x,k;
ll b[5005],dp[5005][5005],res = -1e15;
set<pair<ll,ll> > w[5005];

int main()
{
    cin>>n>>k>>x;
    for(int i = 0 ; i < n ; ++i) scanf("%d",b+i);
    for(int i = 0 ; i < n ; ++i){
        for(int j = 0 ; j < n ; ++j) dp[i][j] = -1e15;
    }
    dp[0][1] = b[0];
    w[1].insert({dp[0][1],0});
    for(int i = 1 ; i < n ; ++i){
        if(i <= k - 1){
            dp[i][1] = b[i];
        }
        for(int j = i + 1 ; j >= 1 ; --j){
            if(!w[j-1].empty() ){
                dp[i][j] = max(dp[i][j],w[j-1].rbegin()->first + b[i]);
            }
            if(w[j].size() == k) w[j].erase({dp[i-k][j],i-k});
            w[j].insert({dp[i][j],i});
        }
    }
    for(int i = n - 1 ; i > n - 1 - k ; --i) {
        res = max(res,dp[i][x]);
    }
    if(res < 1) cout<<-1<<endl;
    else cout<<res<<endl;
    return 0;
}

单调队列优化,双端队列实现:

hard version AC

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
int n,x,k;
ll b[5005],dp[5005][5005],res = -1e15;
deque<pair<ll,ll> > w[5005];

int main()
{
    cin>>n>>k>>x;
    for(int i = 0 ; i < n ; ++i) scanf("%d",b+i);
    for(int i = 0 ; i < n ; ++i){
        for(int j = 0 ; j < n ; ++j) dp[i][j] = -1e15;
    }
    dp[0][1] = b[0];
    w[1].push_front({dp[0][1],0});
    for(int i = 1 ; i < n ; ++i){
        if(i <= k - 1){
            dp[i][1] = b[i];
        }
        for(int j = i + 1 ; j >= 1 ; --j){
            if(!w[j-1].empty() ){
                dp[i][j] = max(dp[i][j],w[j-1].back().first + b[i]);
            }
            while(!w[j].empty() && w[j].back().second <= i - k) w[j].pop_back();
            while(!w[j].empty() && w[j].front().first < dp[i][j]) w[j].pop_front();
            w[j].push_front({dp[i][j],i});
        }
    }
    for(int i = n - 1 ; i > n - 1 - k ; --i) {
        res = max(res,dp[i][x]);
    }
    if(res < 1) cout<<-1<<endl;
    else cout<<res<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值