kuangbin带我飞(基础dp)

001.Max Sum Plus Plus


题目链接 https://vjudge.net/problem/HDU-1024

(dp[i][j])为前j个数将其j为其中一组,分为i组每组相加的和。

这状态转移方程为(:dp[i][j]=max(dp[i][j-1]+a[i],max(dp[i-1][k])+a[i]))

其中(k<=j-1),

复杂度为(O(mn^{2})),显然算法超时。

我们可以对(max(dp[i-1][k]))把这层优化掉,即用Max[j]来前j个数分成i组最大的和。

最后状态转移方程为(:d[j]=max(dp[j-1]+a[i]+Max[j-1]+a[i]))

代码如下(:)

#include <iostream>
using namespace std;
typedef long long ll;
int m,n;
ll a[1000007],dp[1000007],Mx[1000007],Max[1000007];
int main(){
    while(scanf("%d %d",&m,&n)!=EOF){
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
        }
        for(int i=0;i<=n;i++){
            dp[i]=Max[i]=0;
            Mx[i]=-1e18;
        }
        for(int i=1;i<=m;i++){
            for(int j=i;j<=n;j++){
                dp[j]=max(dp[j-1]+a[j],Max[j-1]+a[j]);
                Mx[j]=max(dp[j],Mx[j-1]);
            }
            for(int j=i;j<=n;j++){
                Max[j]=Mx[j];
                Mx[j]=-1e18;
            }
        }
        printf("%lld
",Max[n]);
    }
}

002.MonkeyandBanana


题目链接:https://vjudge.net/problem/HDU-1069#author=prayerhgq

解法一:(dfs+记忆化)

枚举每个箱子的六种放法能否放在之前的箱子上,即这个箱子被之前的箱子长宽都小,直到不能放箱子即返回高度。

直接枚举超时,加上记忆化搜索即可。

#include <iostream>
#include <cstring>
using namespace std;
struct node{
    int l,w,h;
}pre[100];
int n,tot,dp[100][10];
int dfs(int p,int s){
    if(dp[p][s]!=-1) return dp[p][s];
    int ans=0,l,w;
    if(s==1){
        l=pre[p].l,w=pre[p].w;
    }else if(s==2){
        l=pre[p].w,w=pre[p].l;
    }else if(s==3){
        l=pre[p].l,w=pre[p].h;
    }else if(s==4){
        l=pre[p].h,w=pre[p].l;
    }else if(s==5){
        l=pre[p].w,w=pre[p].h;
    }else if(s==6){
        l=pre[p].h,w=pre[p].w;
    }
    for(int i=1;i<=n;i++){
        if(pre[i].l<l&&pre[i].w<w){
            ans=max(ans,dfs(i,1)+pre[i].h);
        }
        if(pre[i].w<l&&pre[i].l<w){
            ans=max(ans,dfs(i,2)+pre[i].h);
        }
        if(pre[i].l<l&&pre[i].h<w){
            ans=max(ans,dfs(i,3)+pre[i].w);
        }
        if(pre[i].h<l&&pre[i].l<w){
            ans=max(ans,dfs(i,4)+pre[i].w);
        }
        if(pre[i].w<l&&pre[i].h<w){
            ans=max(ans,dfs(i,5)+pre[i].l);
        }
        if(pre[i].h<l&&pre[i].w<w){
            ans=max(ans,dfs(i,6)+pre[i].l);
        }
    }
    return dp[p][s]=ans;
}
int main(){
    while(scanf("%d",&n)&&n!=0){
        int ml=0;
        for(int i=1;i<=n;i++){
            scanf("%d %d %d",&pre[i].l,&pre[i].w,&pre[i].h);
            ml=max(ml,max(pre[i].h,max(pre[i].w,pre[i].l)));
        }
        memset(dp,-1,sizeof(dp));
        int res=0;
        for(int i=1;i<=n;i++){
            res=max(res,dfs(i,1)+pre[i].h);
            res=max(res,dfs(i,2)+pre[i].h);
            res=max(res,dfs(i,3)+pre[i].w);
            res=max(res,dfs(i,4)+pre[i].w);
            res=max(res,dfs(i,5)+pre[i].l);
            res=max(res,dfs(i,6)+pre[i].l);

        }
        printf("Case %d: maximum height = %d
",++tot,res);
    }  
}

解法二 dp

由题意易得,每个箱子的六个形态最多用一次,这就相当于有(6*n)个箱子

前个箱子的长宽要大于后个箱子,那么就先对每个箱子按长度由大到小排序

这样我们能推出状态转移方程为(:d[i]=max(d[i],d[j]+pre[i].h))

其中d[i]为选前i个箱子堆成的高度,其中i箱子在最上面,(pre[i].h)为第i个箱子的高度

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n,cnt,dp[1000];
struct node{
    int l,w,h;
}pre[1000];
bool cmp(node x,node y){
    return x.l>y.l;
}
int main(){
    while(scanf("%d",&n)&&n!=0){
        int tot=0;   
        for(int i=1;i<=n;i++){
            int l,w,h;
            scanf("%d %d %d",&l,&w,&h);
            pre[++tot].l=l,pre[tot].w=w,pre[tot].h=h;
            pre[++tot].l=w,pre[tot].w=l,pre[tot].h=h;
            pre[++tot].l=l,pre[tot].w=h,pre[tot].h=w;
            pre[++tot].l=h,pre[tot].w=l,pre[tot].h=w;
            pre[++tot].l=h,pre[tot].w=w,pre[tot].h=l;
            pre[++tot].l=w,pre[tot].w=h,pre[tot].h=l;
        }
        memset(dp,0,sizeof(dp));
        sort(pre+1,pre+1+tot,cmp);
        int ans=0;
        for(int i=1;i<=tot;i++){
            dp[i]=pre[i].h;
            for(int j=1;j<=i-1;j++){
                if(pre[j].w>pre[i].w&&pre[j].l>pre[i].l)
                    dp[i]=max(dp[i],dp[j]+pre[i].h);
            }
            ans=max(ans,dp[i]);
        }
        printf("Case %d: maximum height = %d
",++cnt,ans);
    }
}

003.Super Jumping! Jumping! Jumping!


LIS变形,状态转移方程 dp[i]为前i个数以第i个数结尾的上升子序列的和。

状态转移方程为:(dp[i]=max(dp[i],dp[j]+a[i]))

最后取(ans=max(dp[i]))

#include <iostream>
using namespace std;
typedef long long ll;
int n;
ll a[1007],dp[1007];
int main(){
    while(scanf("%d",&n)&&n!=0){
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
        }
        ll ans=0;
        for(int i=1;i<=n;i++){
            dp[i]=a[i];
            for(int j=1;j<=i-1;j++){
                if(a[j]<a[i]){
                    dp[i]=max(dp[i],dp[j]+a[i]);
                }
            }
            ans=max(ans,dp[i]);
        }
        printf("%lld
",ans); 
    }
}

004.Piggy-Bank


完全背包问题变形。需注意dp[i]初始化为(1e9),(dp[0]=0)

状态转移方程为:(dp[j]=max(dp[j],dp[j-v[i]]+w[i]))

其中(jge v[i])(dp[j-v[i]] e1e9)

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int n,a[1000007],dp[1000007],cnt;
int main(){
    while(scanf("%d",&n)!=EOF){
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            dp[i]=0;
        }
        cnt=0;
        for(int i=1;i<=n;i++){
            int inx=lower_bound(dp,dp+cnt,a[i])-dp;
            dp[inx]=a[i];
            if(inx==cnt){
                cnt++;
            }
        }
        printf("%d
",cnt);
    }
}

005.免费馅饼


暴搜,每秒能有三种状态,在当期坐标(p),在(p-1)坐标,在(p+1)坐标。

暴搜超时,最后加上记忆化即可。

#include <iostream>
#include <cstring>
using namespace std;
int n,mx,dp[20][100007],tem[20][100007];
int dfs(int p,int tim){
    if(tim>mx){
        return 0;
    }
    if(dp[p][tim]!=-1) return dp[p][tim];
    int ans=0;
    if(p+1<=10) ans=max(ans,dfs(p+1,tim+1)+tem[p][tim]);
    if(p-1>=0) ans=max(ans,dfs(p-1,tim+1)+tem[p][tim]);
    ans=max(ans,dfs(p,tim+1)+tem[p][tim]);
    return dp[p][tim]=ans;
}
int main(){
    while(scanf("%d",&n)&&n!=0){
        mx=0;
        memset(tem,0,sizeof(tem));
        memset(dp,-1,sizeof(dp));
        for(int i=1;i<=n;i++){
            int t,x;
            scanf("%d %d",&x,&t);
            if(t>mx) mx=t;
            tem[x][t]++;
        }
        printf("%d
",dfs(5,0));
    }
}

006.Tickets


一维递推。

状态转移方程(dp[i]=max(dp[i-1]+a[i],dp[i-2]+b[i]))

或者记忆化搜索

#include <iostream>
#include <cstring>
using namespace std;
int n,k,s[2007],d[2007],dp[2007];
int dfs(int p){
    if(p>k){
        return 0;
    }
    if(dp[p]!=-1) return dp[p];
    int ans=1e9;
    ans=min(ans,dfs(p+1)+s[p]);
    if(p!=k) ans=min(ans,dfs(p+2)+d[p]);
    return dp[p]=ans;
}
int main(){
    scanf("%d",&n);
    while(n--){
        scanf("%d",&k);
        memset(dp,-1,sizeof(dp));
        for(int i=1;i<=k;i++){
            scanf("%d",&s[i]);
        }
        for(int i=1;i<=k-1;i++){
            scanf("%d",&d[i]);
        }
        int t=8*3600+dfs(1);
        int go=t/(3600*12);
        int h=(t/3600)%12;
        int m=(t/60)%60;
        int s=t%60;
        if(go%2==0){
            printf("%02d:%02d:%02d am
",h,m,s);
        }else{
            printf("%02d:%02d:%02d pm
",h,m,s);
        }
    }
}

007.最少拦截系统


题意是要找最少条非严格下降子序列,可转换成找一条严格上升子序列。

复杂度(o(n^2))的就不赘述了。

下面说说(o(nlogn))的做法。

首先定义一下dp[i]为长度为i的上升子序列第i个数的值

如果想要一个上升子序列尽可能长,那么dp[i]的数尽可能小,那么他后面能接的数也会尽可能的多。

例如一个序列:389 207 155 300 299 170 158 65。

我们要找它的最长上升子序列。首先在dp数组上找大于或等于389的数,将他插入dp数组。

(dp={389})

找大于或等于207的数,我们发现207能代替389。

(dp={207})

找大于或等于155的数。

(dp={155})

找大于或等于300的数。

(dp={155,300})

找大于或等于299的数。

(dp={155,299})

找大于或等于170的数。

(dp={155,170})

找大于或等于158的数。

(dp={155,158})

找大于或等于207的数

(dp={155,207})

所以最长上升子序列的长度为2。

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int n,a[1000007],dp[1000007],cnt;
int main(){
    while(scanf("%d",&n)!=EOF){
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            dp[i]=0;
        }
        cnt=0;
        for(int i=1;i<=n;i++){
            int inx=lower_bound(dp,dp+cnt,a[i])-dp;
            dp[inx]=a[i];
            if(inx==cnt){
                cnt++;
            }
        }
        printf("%d
",cnt);
    }
}

008.Jury Compromise


(dp[i][j][k])为前i个人选j个,(k=a[i]-b[i])(dp[i][j][k])的值为前i个(a[i])(b[i])的和。

有选或不选两个状态转移。

选的话(dp[i][j][k]=dp[i-1][j-1][k+(a[i]-b[i])])((k+(a[i]-b[i])可能为负数,加个(base)偏移量))。

不选(dp[i][j][k]=dp[i-1][j][k])

当k相同时选前i个a[i]和b[i]最大的

那么状态转移方程为(:dp[i][j][k]=max(dp[i-1][j][k],dp[i-1][j-1][k+(a[i]-b[i])]))

(dp[i-1][j][k]!=dp[i][j][k]),代表选择了第i个人,统计(suma)(sumb),和第(i)个人

#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
#include<cstdio>
#include<cstring>
using namespace std;
const int inf=0x3f3f3f3f;
const int base=400;
int n,m,tot,a[207],b[207],x[27][207],y[27][207];
int dp[207][27][807];
vector <int> ans;
int main(){
    while(scanf("%d %d",&n,&m)!=EOF){
        if(n==0&&m==0) break;
        for(int i=1;i<=n;i++){
            scanf("%d %d",&a[i],&b[i]);
        }
        memset(dp,-inf,sizeof(dp));
        ans.clear();
        dp[0][0][base]=0;
        for(int i=1;i<=n;i++){
            for(int j=0;j<=m;j++){
                for(int k=0;k<=800;k++){
                    dp[i][j][k]=dp[i-1][j][k];
                    int t=k+(a[i]-b[i]);
                    if(t<0||t>=800) continue;
				    if(j<1) continue; 
                    if(k+a[i]-b[i]>=0)    
                        dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-1][t]+a[i]+b[i]);
                }
            }
        }
        int v=0;
        while(dp[n][m][base+v]<0&&dp[n][m][base-v]<0) v++;
        if (dp[n][m][base-v]>dp[n][m][base+v]){
    		v=base-v;		
        }else{
            v=base+v; 
        }
        int i=n,j=m,suma=0,sumb=0;
        while(j){
            if(dp[i][j][v]==dp[i-1][j][v]){
                i--;
            }else{
                suma+=a[i];
                sumb+=b[i];
                ans.push_back(i);
                v=v+(a[i]-b[i]);
                i--,j--;
            }
        }
        sort(ans.begin(),ans.end());
        printf("Jury #%d 
",++tot);
        printf("Best jury has value %d for prosecution and value %d for defence: 
",suma,sumb);
        for(int i=0;i<ans.size();i++){
            printf("%d ",ans[i]);
        }
        printf("
");
    }
}

009.Common Subsequence


最长公共子序列板子题。

(dp[i][j])(s)串的前(i)的子序列和(t)串前(j)的子序列的最长公共子序列。

(s[i]==t[j])时。

(dp[i][j]=dp[i-1][j-1]+1)

(s[i]!=t[j])时。

(dp[i][j]=max(dp[i-1][j],dp[i][j-1]))

#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,dp[1000][1000];
char s1[1000],s2[1000];
int main(){
    while(cin>>(s1+1)>>(s2+1)){
        n=strlen(s1+1);
        m=strlen(s2+1);
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(s1[i]==s2[j]){
                    dp[i][j]=dp[i-1][j-1]+1;
                }else{
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        printf("%d
",dp[n][m]);
    }
}

010.Help Jimmy


离散+记忆化搜索。

#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
int T,N,X,Y,MAX,dp[1007][2007];
struct node{
    int x,y,h;
}pre[1007];
vector <int> ho;
bool cmp(node x,node y){
    return x.h>y.h;
}
int get(int x){
    return lower_bound(ho.begin(),ho.end(),x)-ho.begin();
}
int dfs(int pos,int s){
    int ans=1e9,ok1=1,ok2=1;
    //cout<<"pos="<<pos<<" s="<<s<<endl;
    if(pos==N){
        return 0;
    }
    if(dp[pos][s]!=-1) return dp[pos][s];
    for(int i=1;i<=N;i++){
        if(pre[i].h>=pre[pos].h) continue;
        int l=pre[pos].x,r=pre[pos].y;
        if(ok1==1&&l>=pre[i].x&&l<=pre[i].y){
            ok1=0;
            if(abs(pre[pos].h-pre[i].h)<=MAX)    
                ans=min(ans,dfs(i,l)+abs(ho[s]-ho[l])+abs(pre[pos].h-pre[i].h));
        }
        if(ok2==1&&r>=pre[i].x&&r<=pre[i].y){
            ok2=0;
            if(abs(pre[pos].h-pre[i].h)<=MAX)
                ans=min(ans,dfs(i,r)+abs(ho[r]-ho[s])+abs(pre[pos].h-pre[i].h));
        }
        if(ok1==0&&ok2==0) break;
    }
    return dp[pos][s]=ans;
}
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d %d %d %d",&N,&X,&Y,&MAX);
        ho.clear();
        for(int i=1;i<=N;i++){
            scanf("%d %d %d",&pre[i].x,&pre[i].y,&pre[i].h);
            ho.push_back(pre[i].x);
            ho.push_back(pre[i].y);
        }
        pre[N+1].x=-20000,pre[N+1].y=20000,pre[N+1].h=0;
        ho.push_back(pre[N+1].x),ho.push_back(pre[N+1].y);
        ho.push_back(X);
        N=N+1;
        ho.erase(unique(ho.begin(),ho.end()),ho.end());
        sort(ho.begin(),ho.end());
        for(int i=1;i<=N;i++){
            pre[i].x=lower_bound(ho.begin(),ho.end(),pre[i].x)-ho.begin();
            pre[i].y=lower_bound(ho.begin(),ho.end(),pre[i].y)-ho.begin();
        }
        memset(dp,-1,sizeof(dp));
        sort(pre+1,pre+1+N,cmp);
        int res=0;
        for(int i=1;i<=N;i++){
            if(get(X)>=pre[i].x&&get(X)<=pre[i].y){    
                res=dfs(i,get(X))+abs(Y-pre[i].h);
                break;
            }
        }
        printf("%d
",res);
    }
}

011.Longest Ordered Subsequence


最长上升子序列,没什么好讲的

#include <iostream>
using namespace std;
int n,a[1007],d[1007];
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    int res=0;
    for(int i=1;i<=n;i++){
        d[i]=1;
        for(int j=1;j<=i-1;j++){
            if(a[j]<a[i])
                d[i]=max(d[i],d[j]+1);
        }
        res=max(res,d[i]);
    }
    printf("%d
",res);
}

012.Treats for the Cows

区间dp或者记忆化搜索

解法一:

#include <iostream>
using namespace std;
typedef long long ll;
int n;
ll a[2007],dp[2007][2007];
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
    }
    for(int len=n-2;len>=0;len--){
        
        for(int i=1;i+len<=n;i++){
            int j=i+len;
            ll res1=0,res2=0;
            if(j+1<=n) res1=dp[i][j+1]+(n-len-1)*a[j+1];
            if(i-1>=1) res2=dp[i-1][j]+(n-len-1)*a[i-1];
            dp[i][j]=max(res1,res2);
            //printf("dp[%d][%d]=%d ",i,j,dp[i][j]);
        }
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        ans=max(ans,dp[i][i]+n*a[i]);
    }
    printf("%lld
",ans);
}

解法二:

#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
int n;
ll a[2007],dp[2007][2007];
ll dfs(int x,int y){
    if(y<x){
        return 0;
    }
    if(dp[x][y]!=-1) return dp[x][y];
    ll ans=0;
    ans=max(dfs(x,y-1)+(n-abs(y-x))*a[y],dfs(x+1,y)+(n-abs(y-x))*a[x]);
    return dp[x][y]=ans;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
    }
    memset(dp,-1,sizeof(dp));
    printf("%lld
",dfs(1,n));
}

013.FatMouse and Cheese

二维最长上升子序列

#include <iostream>
#include <cstring>
#define judge(x,y) x>=1&&x<=n&&y>=1&&y<=n
using namespace std;
int n,m,dp[107][107],a[107][107];
int dx[]={1,-1,0,0};
int dy[]={0,0,-1,1};
int dfs(int x,int y){
    if(dp[x][y]!=-1) return dp[x][y];
    int ans=a[x][y];
    for(int i=0;i<4;i++){
        for(int j=1;j<=m;j++){
            int nx=x+j*dx[i],ny=y+j*dy[i];
            if(judge(nx,ny)&&a[x][y]<a[nx][ny]){
                ans=max(ans,dfs(nx,ny)+a[x][y]);
            }
        }
    }
    return dp[x][y]=ans;
}
int main(){
    while(scanf("%d %d",&n,&m)!=EOF){
        if(n==-1&&m==-1) break;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                scanf("%d",&a[i][j]);
            }
        }
        memset(dp,-1,sizeof(dp));
        printf("%d
",dfs(1,1));
    }
}
原文地址:https://www.cnblogs.com/kksk/p/13904629.html