基础dp1

kuangbin套题 基础dp1

原先分配任务时dp并非我负责,但是最近比赛表明数论选手的压力太大,于是决定开坑从零自学dp。
听缪神说kuangbin套题能很快提升水平,那不多说,开搞吧。
链接: kuangbin带你飞 专题12 基础dp1.

dp

Dynamic Programming,动态规划。用于决策问题,在贪心无法贪到最优解的情况下,如果能找到一种无后效性的决策方法,就可以尝试用dp进行求解。
某个方面来说dp和暴力其实有异曲同工之处。


A - Max Sum Plus Plus

多组数据,输入直到文件结束。每一行有一个n和一个m,以及由n的数字组成的序列。要求从其中挑出m段连续且不重复的序列,使他们的和最大。

转移方程
dp[i][j]=max(dp[i][j-1],max(dp[i-1][k]))+a[j] (i-1 <= k <= j-1)

转移方程不是很难写,但是内存卡得有点难受。
用Max把max (dp[i-1][k])记录下来,然后二维滚一维。
代码:

#include "bits/stdc++.h"
using namespace std;
int dp[100005];
int Max[100005];
//dp[i][j]=max(dp[i][j-1],max(dp[i-1][k]))+a[j] (i-1 <=  k <= j-1)
int main(){
    //freopen("std1.in","r",stdin);
    //freopen("std1.out","w",stdout);
    int n,m;
    while(~scanf("%d%d",&m,&n)){
        int ans=-0x3f3f3f3f;
        for(int i=0;i<=m;i++)
            Max[i]=-0x3f3f3f3f,dp[i]=-0x3f3f3f3f;
        Max[0]=0;
        for(int i=1;i<=n;i++){
            int tmp;
            scanf("%d",&tmp);
            for(int j=m;j>=1;j--){
                dp[j]=max(dp[j],Max[j-1])+tmp;
                Max[j]=max(Max[j],dp[j]);
            }
            ans=max(ans,dp[m]);
        }
        printf("%d\n",ans);
    }


}

B - Ignatius and the Princess IV

找出一串数字中出现次数过半的数字。
应该会打代码的都会做吧,直接跳过。


C - Monkey and Banana

给与一个n,再给予n种积木,每种积木都有长宽高三个数值, 允许旋转木块,将木块搭高,要求下方的木块的长宽严格大于上放的木块,问最高高度是多少?

首先将一个木块的旋转结果拆成6种无法旋转的木块,这样就不需要考虑木块的旋转了,然后对宽度(高度)进行排序,这样排在前面的木块定不会放在之后的木块之下,满足了无后效性。

转移方程: dp[i]=max(dp[i],dp[j]+h[i]);
代码:

#include "bits/stdc++.h"
using namespace std;
const int inf =0x3f3f3f3f;
typedef struct {
    int x,y,z;
}BLOCK;
vector<BLOCK> d;
int dp[1200];
int main(){
    int n,tot=0;
    while(scanf("%d",&n),n){
        tot++;
        memset(dp,0,sizeof(dp));
        d.clear();
        d.push_back({inf,inf,0});
        for(int i=1;i<=n;i++){
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            d.push_back({a,b,c});
            d.push_back({a,c,b});
            d.push_back({b,a,c});
            d.push_back({b,c,a});
            d.push_back({c,a,b});
            d.push_back({c,b,a});
        }
        sort(d.begin(),d.end(),[](BLOCK a,BLOCK b){
            if(a.x!=b.x)
                return a.x>=b.x;
            else
                return a.y>=b.y;
        });
        int nn=6*n,ans=0;
        for(int i=1;i<=nn;i++){
            dp[i]=d[i].z;
            for(int j=1;j<i;j++){
                if(d[i].x<d[j].x&&d[i].y<d[j].y){
                    dp[i]=max(dp[i],dp[j]+d[i].z);
                    ans=max(ans,dp[i]);
                }
            }
        }
        printf("Case %d: maximum height = %d\n",tot,ans);
    }
}

D - Doing Homework (状态压缩)

给一个n(n<15),之后给n个作业,每个作业有一个截止时间和一个完成时间,每次交作业如果超过了截止时间,会扣(提交时间-截止时间)的分数,问按什么顺序做扣分最少?

把作业是否完成的状态用二进制表示,完成用1未完成用0,那么可以用一个不大于2^15的数字表示所有的作业状态。
如果状态从小跑到大,那么跑每一个状态之前一定会跑过所有的可能到这个情况的状态。那么只要枚举每一个为1的位置,去其中的最小值就是当前情况的最优解。

转移方程:
dp[i]=max(dp[i],dp[i-1<<j]+time)
time = dp[i-1<<j].time + work[j].t - work[j].d;
j=1,2,3,…,n && (i & 1<<j) ==1
代码:

#include "bits/stdc++.h"
using namespace std;
const int inf =0x3f3f3f3f;
struct {
    string s;
    int d,t;
}work[20];
struct {
    int time,from,val;
}dp[1<<16];
int main() {
    int tot;
    scanf("%d",&tot);
    while(tot--){
        int n; scanf("%d",&n);
        for(int i=1;i<=(1<<n)-1;i++)
            dp[i].val=0x3f3f3f3f;
        for(int i=0;i<n;i++){
            char tmp[150];
            int a,b;
            scanf("%s%d%d",tmp,&a,&b);
            work[i]={tmp,a,b};
        }
        for(int i=0;i<= (1<<n)-1;i++) {
            for (int j = n-1; j >= 0; j--) {
                if (i & (1 << j)) {
                    int t = i - (1 << j);
                    int time = dp[t].time + work[j].t - work[j].d;
                    if (max(time,0) + dp[t].val < dp[i].val) {
                        dp[i].val = max(time,0) + dp[t].val;
                        dp[i].time = dp[t].time+work[j].t;
                        dp[i].from = j;
                    }
                }
            }
        }
        stack<int> s;
        int t=(1<<n)-1;
        while(t){
            s.push(dp[t].from);
            t-=(1<<dp[t].from);
        }
        printf("%d\n",dp[(1<<n)-1].val);
        while(!s.empty()){
            printf("%s\n",work[s.top()].s.data());
            s.pop();
        }
    }
}

E - Super Jumping! Jumping! Jumping!

给一个n和有n的数字组成的数列,要求从左往右选择,每次选择的数字严格大于上次选择的数字,问如何选择使选择的数字和最大。

基本线性dp,只需要两个循环即可。因为每个数字都可以是第一个被选择的数字,所以只需要在开头额外放一个数值为0的点即可。
转移方程:
dp[i]=max(dp[i],dp[j]+in[i])
in[j]<in[i]
代码:

#include "bits/stdc++.h"
using namespace std;
int dp[2000];
int in[2000];
int main(){
    int n;
    while(scanf("%d",&n),n){
        memset(dp,0,sizeof(dp));
        int ans=0;in[0]=-0x3f3f3f3f;
        for(int i=1;i<=n;i++){
            scanf("%d",&
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值