「kuangbin带你飞」专题十二 基础DP

Posted luowentao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「kuangbin带你飞」专题十二 基础DP相关的知识,希望对你有一定的参考价值。


layout: post
title: 「kuangbin带你飞」专题十二 基础DP
author: "luowentaoaa"
catalog: true
tags:
mathjax: true
- kuangbin
- 动态规划


传送门

A.HDU1024 Max Sum Plus Plus

题意

给你N个数,然后你分成M个不重叠部分,并且这M个不重叠部分的和最大.

思路

动态规划最大m字段和,dp数组,dp[i][j]表示以a[j]结尾的,i个字段的最大和

两种情况:1.第a[j]元素单独作为第i个字段
        2.第a[j]元素和前面的字段共同当做第i个字段

得到状态转移方程:dp[i][j]=max( dp[i][j-1]+a[j] , max(dp[i-1][t])+a[j]);

但是实际情况是,时间复杂度和空间复杂度都是相当的高,所以要进行时间和空间的优化:
    将每次遍历的时候的max(dp[i-1][t]) 用一个数组d储存起来,这样就能省去寻找max(dp[i-1][t])的时间,
    这样状态转移方程就变成了 dp[i][j]=max( dp[i][j-1]+a[j] , d[j-1]+a[j]), 会发现dp数组的可以
    省去一维,因为每次都是和前一次的状态有关,所以可以记录前一次状态,再用一个变量tmp记录下dp[i][j-1],
    这样方程就变成了 dp[j]=max( num+a[j] , d[j-1]+a[j]);这样就可以化简一下就是:
    dp[j]= max( num , d[j-1])+a[j];
    在之后还要保存前面m-1的情况的最大状态,d[j-1]=ma;等于只有M-1组而且到这个位置时候的值,注意不能最大,因为最大虽然值大但是不保证是M-1个
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int dp[maxn];
int a[maxn];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int m,n;
    while(cin>>m>>n){
        for(int i=1;i<=n;i++)cin>>a[i];
        int ma=0;
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=m;i++){
            int num=0;
            for(int j=1;j<=i;j++)num+=a[j];
            ma=num;
            for(int j=i+1;j<=n;j++){
                num=max(num,dp[j-1])+a[j];//前面的和,自己组成一个
                dp[j-1]=ma;
                ma=max(ma,num);
            }
        }
        cout<<ma<<endl;
    }
    return 0;
}

B.HDU1029 Ignatius and the Princess IV

题意

给你n个数字,你需要找出出现至少(n+1)/2次的数字 现在需要你找出这个数字是多少?

方法一

直接用map记录次数然后扫一遍

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
map<int,int>mp;
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n){
        int a;
        mp.clear();
        for(int i=0;i<n;i++){
            cin>>a;
            mp[a]++;
        }
        map<int,int>::iterator it;
        int maid,ma=0;
        for(it=mp.begin();it!=mp.end();it++){
            if(it->second>=(n+1)/2){
                maid=it->first;
            }
        }
        cout<<maid<<endl;
    }
    return 0;
}

方法二

摩尔投票法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n){
        int k,sum=0,a;
        while(n--){
            cin>>a;
            if(!sum)k=a,sum++;
            else if(k==a)sum++;
            else{
                sum--;
            }
        }
        cout<<k<<endl;
    }
    return 0;
}

方法三

排序后输出中间位置的数

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int a[maxn];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n){
        for(int i=0;i<n;i++)
            cin>>a[i];
        sort(a,a+n);
        cout<<a[(n+1)/2]<<endl;
    }
    return 0;
}

C.HDU1069 Monkey and Banana

题意

三维的最长带全严格上升子序列N个无限个数的三维方体,三维方体可以任意旋转(长宽高可以替换),现在让你求出这些正方体可以搭的最高高度,要求上面的矩形的长和宽必须严格小于下面的矩形的长宽,类似与三维的最长带全严格上升子序列

思路

一对长宽高可以构造出六种不同的正方体(长:三个取一个,宽:两个取一个,高:被固定了);然后根据长来排序,长相同根据宽排序,高不影响不管,依次判断最小面的是哪一个矩形,然后DP出这个矩形在下面时上面放那个矩形的最大值,最后求出一个最大值就行

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
struct node{
    int x,y,z;
    node(int x,int y,int z):x(x),y(y),z(z){};
};
int cmp(node a,node b){
    if(a.x==b.x)return a.y<b.y;
    else return a.x<b.x;
}
vector<node>ve;
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    int t=1;
    while(cin>>n&&n){
        ve.clear();
        for(int i=0;i<n;i++){
            int a,b,c;
            cin>>a>>b>>c;
            ve.push_back(node(a,b,c));ve.push_back(node(a,c,b));
            ve.push_back(node(b,a,c));ve.push_back(node(b,c,a));
            ve.push_back(node(c,a,b));ve.push_back(node(c,b,a));
        }
        int len=ve.size();
        sort(ve.begin(),ve.end(),cmp);
        int sum=0;
        for(int i=0;i<len;i++){
            int ma=0;
           // cout<<"i="<<i<<" ";
           // cout<<ve[i].x<<" "<<ve[i].y<<" "<<ve[i].z<<endl;
            for(int j=0;j<i;j++){
                if(ve[i].x>ve[j].x&&ve[i].y>ve[j].y&&ve[j].z>ma){
                    ma=ve[j].z;
                }
            }
            ve[i].z=ma+ve[i].z;
            sum=max(sum,ve[i].z);
        }
        cout<<"Case "<<t++<<": maximum height = "<<sum<<endl;
    }
    return 0;
}

D.HDU1074 Doing Homework

题意

有n个任务,每个任务有一个截止时间,超过截止时间一天,要扣一个分。
求如何安排任务,使得扣的分数最少,多种可能输出字典序最小的

思路

状压DP! 观察到N最大只有15,跟南京网络赛的E一样思路,想到了暴力枚举每个状态;然后枚举每一个在这状态完成的任务,假设他没完成让他在只有这个任务没完成的情况下完成的情况,因为题目要求是字典序小的先输出,所以同样的情况先枚举字典序大的,如果枚举完大的后,发现小的更好,那就可以直接替换了

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int dp[maxn];
int last[16];
int need[16];
int pre[maxn];
int used[maxn];
string s[16];
void init(){
    memset(dp,inf,sizeof(dp));
    memset(used,0,sizeof(used));
}
void out(int x){
    if(!x)return;
    out(x^(1<<pre[x]));
    cout<<s[pre[x]]<<endl;
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int t;
    cin>>t;
    while(t--){
        int n;
        cin>>n;
        init();
        for(int i=0;i<n;i++){
            cin>>s[i]>>last[i]>>need[i];
        }
        dp[0]=0;
        for(int i=1;i<(1<<n);i++){
            for(int j=n-1;j>=0;j--){
                if(!(i&(1<<j)))continue;
                int pp=i^(1<<j);
                int time=used[pp]+need[j]-last[j];
                if(time<0)time=0;
                if(dp[i]>dp[pp]+time){
                    dp[i]=dp[pp]+time;
                    used[i]=used[pp]+need[j];
                    pre[i]=j;
                }
            }
        }
        cout<<dp[(1<<n)-1]<<endl;
        out((1<<n)-1);
    }
    return 0;
}

E.HDU1087 Super Jumping! Jumping! Jumping!

题意

求最大递增子序列的权值和

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int a[1100];
ll dp[1100];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n&&n){
        for(int i=0;i<n;i++)cin>>a[i],dp[i]=a[i];
        ll sum=0;
        ll ma=0;
        for(int i=0;i<n;i++){
            ll num=0;
            for(int j=0;j<i;j++){
                if(a[i]>a[j]&&num<dp[j]){
                    num=dp[j];
                }
            }
            dp[i]=a[i]+num;
            ma=max(dp[i],ma);
        }
        cout<<ma<<endl;
    }
    return 0;
}

F.HDU1114 Piggy-Bank

题意

有一个存钱罐,给出它的重量和装满硬币的重量,然后给出里面装的硬币的种类数,并给出每种硬币的面值和重量,求在给定重量的条件下硬币的最小价值

思路

根据重量从0开始推到给定的重量,每个从这个重量之前的重量找一个硬币的差距,然后选出一个最小值;默认为inf

完全背包:必须装满给出的重量,因此要使dp[0]=0,同时因为求的是最小值,因此其他位置应该是正无穷。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int dp[maxn];
int p[maxn],w[maxn];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int t;
    cin>>t;
    while(t--){
        int e,f;
        cin>>e>>f;
        int num=f-e;
        int n;
        cin>>n;
        memset(dp,inf,sizeof(dp));
        for(int i=0;i<n;i++){
            cin>>p[i]>>w[i];
        }
        for(int i=0;i<=num;i++){
            for(int j=0;j<n;j++){
                if(w[j]>i){/*cout<<"one   "<<"i=="<<i<<" j="<<j<<"w[j]="<<w[j]<<endl;*/continue;}
                else if(dp[i-w[j]]==inf&&w[j]!=i){/*cout<<"two   "<<"i=="<<i<<" j="<<j<<"w[j]="<<w[j]<<endl;*/continue;}
                else{
                    int k=dp[i-w[j]]==inf?p[j]:dp[i-w[j]]+p[j];
                    dp[i]=min(dp[i],k);
                   // cout<<"三   "<<dp[i]<<"i=="<<i<<" j="<<j<<"w[j]="<<w[j]<<endl;
                }
            }
            //cout<<"dp["<<i<<"]="<<dp[i]<<endl;
        }
        if(dp[num]==inf)cout<<"This is impossible."<<endl;
        else cout<<"The minimum amount of money in the piggy-bank is "<<dp[num]<<"."<<endl;
    }
    return 0;
}
//完全背包的代码;
#include <iostream>
using namespace std;
#define INF 2000000
int main()
{
    int t;
    int n;
    int w,wa,wb;
    int value[505],weight[505];
    int dp[10005];
    while(cin>>t)
    {
        while(t--)
        {
            cin>>wa>>wb;
            w = wb-wa;
            cin>>n;
            for(int i=0;i<n;i++)
            {
                cin>>value[i]>>weight[i];
            }
            for(int i=0;i<=w;i++)dp[i] = INF;
            dp[0] = 0;
            for(int i=0;i<n;i++)
            {
                for(int j=weight[i];j<=w;j++)
                {
                    if(dp[j]>(dp[j-weight[i]]+value[i]))dp[j] = dp[j-weight[i]]+value[i];
                }
            }
            if(dp[w]>=INF)cout<<"This is impossible."<<endl;
            else{
                cout<<"The minimum amount of money in the piggy-bank is ";
                cout<<dp[w]<<"."<<endl;
            }
        }
    }
    return 0;
}

G.HDU1176 免费馅饼

题意

中文题

思路

二维DP,反向DP,
[ DP[i][j]=max(dp[i+1][j],dp[i+1][j-1],dp[i+1][j+1])+dp[i][j] ]

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e5+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
ll dp[maxn][12];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n&&n){
        memset(dp,0,sizeof(dp));
        int m=0;
        for(int i=0;i<n;i++){
            int a,b;
            cin>>a>>b;
            dp[b][a]++;
            if(m<b)m=b;
        }
        for(int i=m-1;i>=0;i--){
            for(int j=0;j<=10;j++){
                if(j==0){
                    dp[i][j]+=max(dp[i+1][j],dp[i+1][j+1]);
                }
                else if(j==10){
                    dp[i][j]+=max(dp[i+1][j],dp[i+1][j-1]);
                }
                else{
                    dp[i][j]+=max(dp[i+1][j],max(dp[i+1][j-1],dp[i+1][j+1]));
                }
            }
        }
        cout<<dp[0][5]<<endl;
    }
    return 0;
}

H.HDU1260 Tickets

题意

现在有n个人要买电影票,如果知道每个人单独买票花费的时间,还有和前一个人一起买花费的时间,问最少花多长时间可以全部买完票。

思路

关注最后一个买票的人,如果他自己买,那就是选择前面那个人的(和前面的前面那个买还是自己买的)的最小值;如果他和前面的买,那就减去前面自己买的钱,加上一起买的钱,最后选出最小值即可,这里因为考虑的只关于前一个人所以可以开一个二维滚动数组;

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int a[2005],b[2005];
int dp[2][3];
int main()
{
  //  std::ios::sync_with_stdio(false);
  //  std::cin.tie(0);
   // std::cout.tie(0);
    int t;
    cin>>t;
    while(t--){
        int n;
        cin>>n;
        for(int i=1;i<=n;i++)cin>>a[i];
        for(int i=2;i<=n;i++)cin>>b[i];
        dp[1][1]=inf;dp[1][0]=a[1];//0是自己买,1是跟前面的买
        for(int i=2;i<=n;i++){
            dp[i%2][0]=min(dp[(i+1)%2][0],dp[(i+1)%2][1])+a[i];
            dp[i%2][1]=dp[(i+1)%2][0]+b[i]-a[i-1];
        }
        int time=min(dp[n%2][0],dp[n%2][1]);
        int h=8,m=0,s=0;
        h+=time/3600;time%=3600;m=time/60;time%=60;s=time;
        h%=24;
        int flag=0;
        if(h<=12)flag=0;
        else if(h>12){
            flag=1;
        }
        printf("%02d:%02d:%02d %s
",h,m,s,flag?"pm":"am");
    }
    return 0;
}

I.HDU1257 最少拦截系统

思路

本质上是求最长严格上升子序列,模拟成题意,就是

1.第一颗炮弹塞进数组中 拦截系统+1;

2.第二个炮弹大于第一颗炮弹,那就只能拦截系统+1;但是这个炮弹还能打比它小的导弹所以塞进数组中

3.如果第三个炮弹小于等于前面已经射出去的最高的炮弹,那就说明它可以被前面用过的炮弹打中,但是最优的策略是在前面用过的炮弹中选出一个大于它最小的炮弹用来替换,说明这个炮弹现在的位置在这了,如果等于最好,(但是有点题目是不能等于,比如最大不严格上升子序列,那就只能用upper_bound)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
vector<int>ve;
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n){
        ve.clear();
        int len=0;
        for(int i=0;i<n;i++){
            int a;
            cin>>a;
            if(len==0||ve[len-1]<a){//如果不严格的话把小于搞成<=a 说明这个炮弹等于它高度的它也打不中,只能换新炮弹了
                ve.push_back(a);
                len++;
            }
            else{
                int j=lower_bound(ve.begin(),ve.end(),a)-ve.begin();//找出最小最接近的用过的炮弹,数组是递增的。如果是不严格就改成upper
                ve[j]=a;
            }
        }
        cout<<len<<endl;

    }
    return 0;
}

J.HDU1160 FatMouse‘s Speed

题意

找到一个最多的老鼠序列,使得序列中的老鼠的体重满足递增,相应老鼠的速度满足递减。

思路

先按体重递增进行sort排序,然后按照体重找到最长递减子序列即可,用动态规划做比较简单。状态f[i]表示前i个老鼠中的最长递减子序列长度,状态转移方程为f[i] = max{f[j], mice[j].speed > mice[i].speed} + 1, 最后找出最大的f[i]即可。注意此题还需要输出找到的序列中的老鼠的最原始的标号,因此不仅要在刚开始的时候把每个老鼠的最初的序号记下来,还要在进行状态转移的时候把当前的老鼠的位置标记下来。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
struct node{
    int w,s,id;
}my[1005];
int cmp(node a,node b){
    if(a.w==b.w)return a.s>b.s;
    else return a.w<b.w;
}
int dp[1005];
int pre[1005];
void out(int x){
    if(!x)return;
    out(pre[x]);
    cout<<my[x].id<<endl;
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n=1;
    while(cin>>my[n].w>>my[n].s){
        my[n].id=n;
        n++;
    }
    n=n-1;
    for(int i=1;i<=n;i++)dp[i]=1;
    sort(my+1,my+n+1,cmp);
    int maid,malen=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<i;j++){
            if(my[j].w<my[i].w&&my[j].s>my[i].s&&dp[i]<dp[j]+1){
                dp[i]=dp[j]+1;
                pre[i]=j;
                if(dp[i]>malen)maid=i,malen=dp[i];
            }
        }
    }
    cout<<malen<<endl;
    out(maid);
    return 0;
}

K.POJ1015 Jury Compromise

题意

在遥远的国家佛罗布尼亚,嫌犯是否有罪,须由陪审团决定。陪审团是由法官从公众中挑选的。先随机挑选n 个人作为陪审团的候选人,然后再从这n 个人中选m 人组成陪审团。选m 人的办法是:控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从0 到20。为了公平起见,法官选出陪审团的原则是:选出的m 个人,必须满足辩方总分D和控方总分P的差的绝对值|D-P|最小。如果有多种选择方案的|D-P| 值相同,那么选辩控双方总分之和D+P最大的方案即可。

输出:

选取符合条件的最优m个候选人后,要求输出这m个人的辩方总值D和控方总值P,并升序输出他们的编号。
#include<cstdio>
#include<ctype.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
int dp[21][801];
vector<int> path[21][801];
 
int main()
{
    int times=1;
    int subtraction[201],_plus[201];
    int n,m,i,j,k;
    while(~scanf("%d%d",&n,&m) && n && m)
    {
        for(i=0;i<m;++i)
            for(j=0;j<801;++j)
                path[i][j].clear();
        memset(dp,-1,sizeof(dp));
        int d,p;
        for(i = 0; i < n; i++)
        {
            cin>>d>>p;
            subtraction[i] = d-p;
            _plus[i] = d+p;
        }
        int fix = 20*m;
        dp[0][fix] = 0;
        for(k = 0; k < n; k++)
            for(i = m-1; i >= 0; i--)
            {
                for(j = 0; j < 2*fix; j++)
                {
                    if(dp[i][j] >= 0)
                    {
                        if(dp[i+1][j+subtraction[k]] <= dp[i][j] + _plus[k])
                        {
                            dp[i+1][j+subtraction[k]] = dp[i][j] + _plus[k];
                            path[i+1][j+subtraction[k]] = path[i][j];
                            path[i+1][j+subtraction[k]].push_back(k);
                        }
                    }
                }
            }
        for(i = 0; dp[m][fix+i] == -1 && dp[m][fix-i] == -1; i++);
        int temp = (dp[m][fix+i] > dp[m][fix-i]) ? i : -i;
        int sumD = ( dp[m][fix+temp] + temp )/2;
        int sumP = ( dp[m][fix+temp] - temp )/2;
        printf( "Jury #%d
", times++ );
        printf( "Best jury has value %d for prosecution and value %d for defence:
", sumD,sumP);
        for( i=0; i < m; i++ )
            printf( " %d", path[m][fix+temp][i]+1);
        printf( "

" );
 
    }
    return 0;
}

L.POJ1458 Common Subsequence

题意

最长公共子序列

思路

[ if(s[i]==s[j])dp[i][j]=dp[i-1][j-1]+1; else dp[i][j]=max(d[i-1][j],dp[i][j-1]); ]

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e3+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int dp[maxn][maxn];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    char a[1500],b[1500];
    while(cin>>a+1>>b+1){
        int n,m;
        n=strlen(a+1);m=strlen(b+1);
        int k=max(n,m);
        memset(dp,0,sizeof(dp));
        int ma=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(a[i]==b[j]){
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else{
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        cout<<dp[n][m]<<endl;
    }
    return 0;
}

M.POJ1661 Help Jimmy

题意

中文题,小人从最高点跳下

1.可以跳到板子上
2.可以直接跳到地上
3.下落的距离不能超过MAX
4.可以在板子上左右移动,但是要花费时间;
5.问从起点落到地面的最小值

思路

1.可以从下枚举上去到起点
2.对于每一个板子,都可以落到它下面的符合条件的板子(可以正好接住)
3.对于每一个板子,都可以从左边或者右边落下,分别枚举左右边即可
4.假设DP(i-0)表示这个板子从左边落到地面花费的时间
DP(i-1)表示这个板子从右边左到地面花费的时间
一直枚举到最上面的起点即可,复杂度O(n^2)

[ DP[i][0]=max(DP[j][0]+走到左边花费的时间,DP[j][1]+走到右边花费的时间)+从i落到J花费的时间 ]

//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1200;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int x,y,MAX,n;
struct node{
    int l,r;
    int h;
}my[maxn];
int cmp(node a,node b){
    return a.h<b.h;
}
int dp[maxn][2];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int t;
    cin>>t;
    while(t--){
        cin>>n>>x>>y>>MAX;
        my[0].l=-inf;my[0].r=inf;my[0].h=0;
        for(int i=1;i<=n;i++)cin>>my[i].l>>my[i].r>>my[i].h;
        my[n+1].l=x;my[n+1].r=x;my[n+1].h=y;
        sort(my,my+n+1,cmp);
        memset(dp,inf,sizeof(dp));
        dp[1][0]=dp[1][1]=my[1].h;
        dp[0][0]=dp[0][1]=0;
        for(int i=1;i<=n+1;i++){
            for(int j=0;j<i;j++){
                if(my[j].l<=my[i].l&&my[j].r>=my[i].l&&my[i].h-my[j].h<=MAX){
                    if(j==0){
                        dp[i][0]=my[i].h;
                    }
                    else{
                        dp[i][0]=min(dp[j][0]+my[i].l-my[j].l,dp[j][1]+my[j].r-my[i].l)+my[i].h-my[j].h;
                    }
                }
                if(my[j].l<=my[i].r&&my[j].r>=my[i].r&&my[i].h-my[j].h<=MAX){
                    if(j==0){
                        dp[i][1]=my[i].h;
                    }
                    else{
                        dp[i][1]=min(dp[j][0]+my[i].r-my[j].l,dp[j][1]+my[j].r-my[i].r)+my[i].h-my[j].h;
                    }
                }
            }
        }
        cout<<min(dp[n+1][0],dp[n+1][1])<<endl;
    }
    return 0;
}

N.POJ2533 Longest Ordered Subsequence

题意

最长递增子序列

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
vector<int>ve;
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n){
        vector<int>ve;
        int len=0;
        for(int i=0;i<n;i++){
            int a;
            cin>>a;
            if(!len||a>ve[len-1]){
                ve.push_back(a);
                len++;
            }
            else{
                ve[lower_bound(ve.begin(),ve.end(),a)-ve.begin()]=a;
            }
        }
        cout<<len<<endl;
    }
    return 0;
}

O.POJ3186 Treats for the Cows

题意

给出n个数字v(i),每次你可以取出最左边的数字或者取出最右边的数字,一共取n次取完。假设你第i次取的数字是x,那么你可以获得i*x的价值。现在你需要规划取数顺序,使得总价值和最大。

思路

区间DP,方程
[ dp[i][j] = max(dp[i + 1][j] + v[i]*(n-j+i),dp[i][j-1]+v[j]*(n-j+i)) ]

//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int dp[2001][2001];
int v[2001];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n){
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++){
            cin>>v[i];dp[i][i]=v[i];
        }
        for(int i=n;i>=1;i--){
            for(int j=i;j<=n;j++){
                dp[i][j]=max(dp[i+1][j]+v[i]*(n-j+i),dp[i][j-1]+v[j]*(n-j+i));
            }
        }
        cout<<dp[1][n]<<endl;
    }
    return 0;
}

P.HDU1078 FatMouse and Cheese

题意

有一种游戏是的玩法是这样的:
有一个n*n的格子,每个格子有一个数字。
遵循以下规则:
1. 玩家每次可以由所在格子向上下左右四个方向进行直线移动,每次移动的距离不得超过m
2. 玩家一开始在第一行第一列,并且已经获得该格子的分值
3. 玩家获得每一次移动到的格子的分值
4. 玩家下一次移动到达的格子的分值要比当前玩家所在的格子的分值要大。
5. 游戏所有数字加起来也不大,保证所有数字的和不会超过int型整数的范围
6. 玩家仅能在n*n的格子内移动,超出格子边界属于非法操作
7. 当玩家不能再次移动时,游戏结束
现在问你,玩家所能获得的最大得分是多少?

思路

记忆化搜索,把每次搜索的结果存在DP里面 dp数组表示,这一个点接下去搜索能搜索到的最大值

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int n,m;
int mp[110][110];
int dp[110][110];
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};
int dfs(int x,int y){
    if(dp[x][y]!=-1)return dp[x][y];///说明这个点已经被用过了;
    else{
        int ans=0;
        for(int i=1;i<=m;i++){///步数限制
            for(int j=0;j<4;j++){///方向限制
                int xx=x+dx[j]*i;
                int yy=y+dy[j]*i;
                if(xx>=1&&xx<=n&&yy>=1&&yy<=n){
                    if(mp[xx][yy]>mp[x][y])
                        ans=max(ans,dfs(xx,yy));
                }
            }
        }
        dp[x][y]=mp[x][y]+ans;
    }
    return dp[x][y];
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    while(cin>>n>>m&&!(n==-1&&m==-1)){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++)
                cin>>mp[i][j];
        }
        memset(dp,-1,sizeof(dp));
        dfs(1,1);
        cout<<dp[1][1]<<endl;
    }
    return 0;
}

Q.HDU2859 Phalanx

题意

给你一个矩阵,只由小写或大写字母构成。求出它的最大对称子矩阵的边长。

其中对称矩阵是一个kk的矩阵,它的元素关于从左下角到右上角的对角线对称。
例如下面这个3
3的矩阵是对称矩阵:
cbx
cpb
zcc

思路

dp ij 表示以点i,j为左下角的最大对称矩阵;发现以下规律

1.对于矩阵的最上面和最右边 i=1或者j=n的点为左下角的对称矩阵最大为1(就是它自己)

2.然后根据1可以得知,第二行最大的矩阵大小为2;并且最大的矩阵比第一个矩阵要多一个左边和右边的大小;

3.得出对于一个以该点为左下角的对称矩阵的最大大小由它的右上角的对称矩阵推出,而且最多比它大一圈;

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int dp[1100][1100];
int n;
char s[1100][1100];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    while(cin>>n&&n){
        for(int i=1;i<=n;i++)cin>>s[i]+1;
        int ma=1;
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)dp[1][i]=dp[i][n]=1;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(i==1||j==n)continue;
                int xx=i,yy=j,num=0;
                while(xx>=1&&yy<=n&&s[xx][j]==s[i][yy]){
                    num++;
                    xx--;yy++;
                }
                int pre=dp[i-1][j+1];
                if(num>=pre+1)dp[i][j]=pre+1;
                else dp[i][j]=num;
                ma=max(ma,dp[i][j]);
              //  cout<<"i="<< i<<" "<<"j="<<j<<" "<<ma<<" "<<dp[i][j]<<endl;
            }
        }
        cout<<ma<<endl;
    }
    return 0;
}

R.POJ3616 Milking Time

题意

M个时间段,每个时间段有V价值,在每个时间段之间必须隔着R的时间,求最大价值和

思路

把时间段的起点排序一下,然后对于每个时间段判断前面的时间段间隔满不满足题意。然后选出价值最大的

//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1200;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int n,m,R;
struct node{
    int l,r;
    int money;
}my[maxn];
int cmp(node a,node b){
    return a.l<b.l;
}
int dp[maxn];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    while(cin>>n>>m>>R){
        for(int i=1;i<=m;i++){
            cin>>my[i].l>>my[i].r>>my[i].money;
        }
        sort(my+1,my+1+m,cmp);
        int ans=0;
        for(int i=1;i<=m;i++){
            int ma=0;
            for(int j=1;j<i;j++){
                if(my[j].r+R<=my[i].l&&ma<dp[j]){
                    ma=dp[j];
                }
            }
            dp[i]=ma+my[i].money;
            ans=max(ans,dp[i]);
        }
        cout<<ans<<endl;
    }
    return 0;
}

S.POJ3666 Making the Grade

题意

一个序列A,让你把它变成不严格递减或者不严格递增的序列B,花费是
[ sum abs(b[i]-a[i]) ]

思路

构造DP【i】【j】表示前面i个元素组成的不严格递减和不严格递增序列,其中J是这个序列中的最大值;

然后通过DP【i-1】求出最小值,中间利用了离散化

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=2200;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
//const int inf=0x3f3f3f3f;
ll a[maxn];
ll dp[maxn][maxn];
int n;
int cnt=0;
vector<ll>ve;
ll abs(ll a){
    if(a>=0)return a;
    else return -a;
}
int cmp(int a,int b){return a>b;}
ll ac(){
    for(int i=1;i<=n;i++){
        ll mi=dp[i-1][0];
        for(int j=0;j<cnt;j++){
            mi=min(mi,dp[i-1][j]);
            dp[i][j]=abs(a[i]-ve[j])+mi;
        }
    }
    ll ans=dp[n][0];
    for(int i=0;i<cnt;i++)ans=min(ans,dp[n][i]);
    return ans;
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i],ve.push_back(a[i]);
    sort(ve.begin(),ve.end());
    ve.erase(unique(ve.begin(),ve.end()),ve.end());
    cnt=ve.size();
    ll ans1=ac();
    sort(ve.begin(),ve.end(),cmp);
    ll ans2=ac();
    ll anw=min(ans1,ans2);
    cout<<anw<<endl;
    return 0;
}

以上是关于「kuangbin带你飞」专题十二 基础DP的主要内容,如果未能解决你的问题,请参考以下文章

[kuangbin带你飞]专题十二 基础DP1 B - Ignatius and the Princess IV

算法系列学习状压dp [kuangbin带你飞]专题十二 基础DP1 D - Doing Homework

「kuangbin带你飞」专题二十二 区间DP

算法系列学习[kuangbin带你飞]专题十二 基础DP1 C - Monkey and Banana

算法系列学习[kuangbin带你飞]专题十二 基础DP1 E - Super Jumping! Jumping! Jumping!

算法系列学习DP和滚动数组 [kuangbin带你飞]专题十二 基础DP1 A - Max Sum Plus Plus