HLJU_DP解题报告

来源:互联网 发布:快速下载淘宝 编辑:程序博客网 时间:2024/06/06 10:55

传送门:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=104104#overview

A题:简单的背包问题

题意:给你每件物品的价值和大米的重量和大米有多少袋,问我们m元钱能最多买多少大米。

分析:最初我们看到这个题有一个想法就是每次买性价比最高的那个,贪心的思想去做这道题,但是我给你一组数据吧

3 15

7 3 1    价格7,重量3,只有1袋

8 3 1

9 5 1

意思是3种大米,15这么多的钱,有7-3,8-3,9-5这种类型的大米,这样的话我们选择性价比最高的显然选择9-5,这时候我们发现我们完全可以选择7-3,8-3这个组合,得到更多的价值。

这个时候我们我们使用动态规划的时候想到什么呢?

我们是不是可以定义一个dp数组?

dp[i+1][j],代表前i件物品且j这么多钱我们最多能得到多重的大米。

如果说我们这样定义dp[i+1][j]的话,我们在选择第i件的时候该怎么转移状态呢?

我们用cost[i],gain[i]来表示这袋米的属性的话,我们对于这袋大米只有两种选择,要么买,要么不买,不买的话dp[i+1][j]=dp[i][j],买的话就是dp[i+1][j]=dp[i][j-cost[i]]+gain[i],对吗?那么我们按照什么规则进行选择呢?这时候肯定是比较一下两种哪个大对吧?那么这个时候我们可以得到这个状态转移方程式

dp[i+1][j]=max(dp[i][j],dp[i][j-cost[i]]+gain[i]);

那么这个时候我们还有一种情况没有考虑到,就是我现在的钱还买不起这袋米的时候那么我们就直接让dp[i+1][j]=dp[i][j]就行了吧?

关于每种大米的数量的处理在代码里会写到

代码如下:

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;#define MAX_N 105int cost[MAX_N],gain[MAX_N],num[MAX_N],dp[MAX_N][MAX_N];int main(void){    int i,j,n,m,T,k;    scanf("%d",&T);    while(T--){        scanf("%d %d",&m,&n);        for(i=0;i<n;i++)            scanf("%d %d %d",&cost[i],&gain[i],&num[i]);        memset(dp,0,sizeof(dp));        for(i=0;i<n;i++){            for(j=1;j<=m;j++){                for(k=0;k<=num[i];k++){//想想为什么k从0开始而不是1,写成1会出什么问题0                    if(k*cost[i]>j){  //买不起的话我们是不是可以直接这样处理?                        dp[i+1][j]=max(dp[i+1][j],dp[i][j]);                        break;                    }                    else dp[i+1][j]=max(dp[i+1][j],dp[i][j-k*cost[i]]+k*gain[i]);//代表我一次买k袋会怎样                }            }        }        printf("%d\n",dp[n][m]);    }}//想想用一维的dp数组怎么写?

B题:最长公共序列问题

题意:给你两个串,输出这个两串的最长公共序列的长度即可(如果让我们继续输出这个公共序列是什么怎么办?这个时候我们怎么处理这个问题呢?

分析:定义dp[i+1][j+1]表示第一个串的前i位和第二个串的前j位的最长公共序列的长度,

如果这样定义的话状态该怎么转移呢?

显然,如果第一个串的第i位和第二个串的第j位相同的时候dp[i+1][j+1]=dp[i][j]+1

如果说这两个位置不相同的时候,那么我们是不是可以这样

dp[i+1][j+1]=max(dp[i+1][j],dp[i][j+1])这样呢?

代码如下:

#include<cstdio>#include<algorithm>#include<cstring>using namespace std;#define MAX_N 1005int dp[MAX_N][MAX_N];char s[MAX_N],ss[MAX_N];int main(void){    int len,lenn,i,j;    while(scanf("%s %s",s,ss)!=EOF){        len=strlen(s);        lenn=strlen(ss);        memset(dp,0,sizeof(dp));        for(i=0;i<len;i++){            for(j=0;j<lenn;j++){                if(s[i]==ss[j]) dp[i+1][j+1]=dp[i][j]+1;//相同的时候                else dp[i+1][j+1]=max(dp[i+1][j],dp[i][j+1]);//不同的时候            }        }        printf("%d\n",dp[len][lenn]);//好好理解一下,要彻底弄明白    }}

C题:最长公共上升序列问题

题意:给你两个整数序列,让我们输出这两个串的最长的上升的公共序列。

分析:

代码如下


D题:数塔问题

题意:给你一个数塔,当我们在一个点的时候我们可以向下走,也可以向下面的一个右边一个走,输出怎样向下走能得到最大的值。

分析:典型的dp问题,我们定义dp[i+1][j+1]表示当前在第i行第j列的最大值是什么。

这样的话我们是不是可以很简单的得到一个dp转移方程式?

dp[i+1][j+1]=max(dp[i][j],dp[i][j+1])+a[i][j]。其中a[i][j]表示在当前这个位置的数是什么

(想想为什么要+1,+1这样处理呢?最后又输出什么呢?)

代码如下:

#include<cstdio>#include<algorithm>#include<cstring>using namespace std;int a[105][105],dp[105][105];int main(void){    int i,j,n,m,T;    scanf("%d",&T);    while(T--){        scanf("%d",&n);        for(i=0;i<n;i++){            for(j=0;j<=i;j++){                scanf("%d",&a[i][j]);            }        }        memset(dp,0,sizeof(dp));        for(i=0;i<n;i++){            for(j=0;j<=i;j++){                dp[i+1][j+1]=max(dp[i][j],dp[i][j+1])+a[i][j];            }        }        printf("%d\n",*max_element(dp[n]+1,dp[n]+n+1));    }}





E题:

题意:

分析:


F题:最大连续字段和问题

题意:给你一个整数串,让我们输出这个串的最大的连续的字段的和,且这个串的长度要最长。

分析:这个问题是dp的经典的问题了,该怎么处理呢?首先我们这样想对于一个点来说,如果说它之前一个点为结尾的的字段是大于等于零的,那么我们是不是可以把当前的这个点加到之前一个点的字段和上呢?那么我们是不是可以知道一每个点结尾的最大字段和是什么呢?这样的话再每次记录一下每个字段和的开始的下标不就行了吗?这样的话我们是不是还可以更优化一点呢?根据我们的要求的话我们是不是每次找到最大的字段和,就更新一下最大值且更新一下字段和区间不就行了吗?

代码如下:

#include<cstdio>#include<algorithm>using namespace std;int a[100005];int main(void){    int i,j,n,m,T,sum,L,R,MAX,l,r,ca=1;    scanf("%d",&T);    while(T--){        scanf("%d",&n);        for(i=0;i<n;i++)            scanf("%d",&a[i]);        sum=a[0];        MAX=a[0];        L=R=l=r=1;        for(i=1;i<n;i++){            if(sum>=0){                sum+=a[i];                r=i+1;            }            else{                sum=a[i];                l=i+1;                r=i+1;            }            if(sum>MAX){                MAX=sum;                L=l;                R=r;            }        }        printf("Case %d:\n%d %d %d\n",ca++,MAX,L,R);        if(T) printf("\n");    }}

G题:最大递增序列和问题

题意:给你一个串,要求我们找到一个递增序列,且这个序列的和最大。

分析:这样的话我们是不是可以定义dp[i+1]表示第i个数结尾的最大递增序列的最大值,那么我们就能很简单的想到一个O(n*n)的算法呢?即访问当前这个数的时候,去它前面找所有比它小的数的最大递增序列的最大值,之后更新dp[i+1]即可吧?

代码如下:

include<cstdio>#include<algorithm>#include<cstring>using namespace std;int dp[1005],a[1005];int main(void){    int i,j,n;    while(scanf("%d",&n)!=EOF){        memset(dp,0,sizeof(dp));        if(n==0) break;        for(i=0;i<n;i++)            scanf("%d",&a[i]);        for(i=0;i<n;i++){            for(j=0;j<i;j++){                if(a[j]<a[i]){                    dp[i]=max(dp[i],dp[j]);                }            }            dp[i]+=a[i];        }        printf("%d\n",*max_element(dp,dp+n));    }}

H题:

题意:

分析:


I题:

题意:

分析:


J题:完全背包问题

题意:有n种物品,每种物品无限,最后让我们输出m这么大对的背包能得到的最大价值

分析:这个题和前面的一道题简直一模一样不是吗?只不过就是每种物品有无限个而已,怎么处理呢?3重循环当然是可以的,但是可能会有超时的风险,我这里给出一种用一维数组优化的写法,(仔细想想为啥)

代码如下:

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int cost[105],gain[105],dp[100005];int main(void){    int n,m,i,j;    while(scanf("%d",&n)!=EOF){        for(i=0;i<n;i++)            scanf("%d %d",&gain[i],&cost[i]);        scanf("%d",&m);        memset(dp,0,sizeof(dp));        for(i=0;i<n;i++){            for(j=1;j<=m;j++){                if(j>=cost[i]){                    dp[j]=max(dp[j],dp[j-cost[i]]+gain[i]);                }            }        }        printf("%d\n",dp[m]);    }}

K题:最大字段和问题

题意:给你一串数,找出这串数的m段不相容的字段和,使得这m段字段和的和最大化,输出这个最大值。

分析:

代码如下:

L题:模拟数塔问题

这个题怕你们不会做故意把题目写成这样

题意:有11个点,分别为(0—10)初始在5这个位置,现在有T个馅饼,会在特定的时间掉在特定的位置掉下,我们每一秒只能向左或向右移动一个位置,现在问你最多能得到多少个馅饼。

分析:这个题是不是很像数塔呢?注意的是每次我们考虑好边界条件。而且这道题是不是逆着考虑想会更好?

我们定义dp[i][j]表示最后i秒在j这个位置能得到最后T-i秒的多少馅饼,(T表示输入的最大时间),可以看看代码好好理解一下

代码如下:

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int dp[100005][11],num[100005][11];int main (void){    int i,j,n,m,a,b,t;    while(scanf("%d",&t)!=EOF){        if(t==0) break;        memset(num,0,sizeof(num));        for(i=0;i<t;i++){            scanf("%d %d",&a,&b);            num[b][a]++;            n=max(b,n);        }        memset(dp,0,sizeof(dp));        for(i=n;i>=0;i--){            for(j=0;j<=10;j++){                if(j==0) dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+num[i][j];//注意一下边界条件                else if(j==10) dp[i][j]=max(dp[i+1][j],dp[i+1][j-1])+num[i][j];//注意一下边界条件                else dp[i][j]=max(dp[i+1][j],max(dp[i+1][j-1],dp[i+1][j+1]))+num[i][j];//注意一下边界条件            }        }        printf("%d\n",dp[0][5]);    }}

这个题目好好想想为什么这样写,对理解dp很有帮助

M题:二维最大递增序列和问题

题意:现在有n种箱子,每种箱子有其对应的长宽高,且每种箱子有无限个,我们先在要用这些箱子搭一座塔,一个箱子上能再放一个箱子的要求是,这个箱子的长比上一个箱子要小,这个箱子的宽比上一个箱子要小,由于我们知道一个箱子能够旋转,那么一个箱子能够得到6种形状,

分析:这样的话,我们是不是可以先对一维进行排序,再对第二维dp找到以这个箱子为顶部的塔的最大高度呢?

代码如下:

#include<cstdio>#include<algorithm>#include<cstring>using namespace std;struct node{    int a,b,c;}A[200];int cmp(node a,node b){    return a.a>b.a;}int dp[200];int main(void){    int k,i,j,n,m,ca=1,L,a,b,c;    while(scanf("%d",&n)!=EOF){        if(n==0) break;        for(k=0,i=0;i<n;i++){            scanf("%d %d %d",&a,&b,&c);            A[k].a=a;            A[k].b=b;            A[k++].c=c;            A[k].a=b;            A[k].b=a;            A[k++].c=c;            A[k].a=a;            A[k].b=c;            A[k++].c=b;            A[k].a=c;            A[k].b=a;            A[k++].c=b;            A[k].a=c;            A[k].b=b;            A[k++].c=a;            A[k].a=b;            A[k].b=c;            A[k++].c=a;        }        sort(A,A+k,cmp);        for(i=0;i<k;i++){            dp[i]=0;            for(j=0;j<i;j++){                if(A[j].a>A[i].a&&A[j].b>A[i].b){                    dp[i]=max(dp[j],dp[i]);                }            }            dp[i]+=A[i].c;        }        printf("Case %d: maximum height = %d\n",ca++,*max_element(dp,dp+k));    }}

N题:最长上升序列问题

题意:不说了

分析:为什么是最长上升序列问题呢?想想吧,不能一直给答案吧。。。。

代码如下:一种简单的求上升序列问题的方法,为什么这样写?问我

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;#define INF 3000005int a[100005],dp[100005];int main(void){    int n,m,i,j;    while(scanf("%d",&n)!=EOF){        for(i=0;i<n;i++)            scanf("%d",&a[i]);        fill(dp,dp+n,INF);        for(i=0;i<n;i++)            *lower_bound(dp,dp+n,a[i])=a[i];        printf("%d\n",lower_bound(dp,dp+n,INF)-dp);    }}

O题:其实是一个贪心的题,和DP没啥关系。。

题意:给你N个课程的结束时间及相应的分数,每门功课学习一天就能得到相应的分数反之不学的话就得不到相应的分数,问我们在所有功课结束的时候我们最少多少学分没得到。

分析:我们肯定要让每一天得到的分数最大化吧?且显然越在前面的天越有用,所以我们是不是可以优先利用好后面的天数?这样我们就能得到一个贪心策略。

代码如下:

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;struct node{    int a,b;}A[1005];int dp[1005];int cmp(node A,node B){    return A.b>B.b;}int main(void){    int n,i,j,ans,T;    scanf("%d",&T);    while(T--){        scanf("%d",&n);        for(i=0;i<n;i++)            scanf("%d",&A[i].a);        for(i=0;i<n;i++)            scanf("%d",&A[i].b);        sort(A,A+n,cmp);        memset(dp,0,sizeof(dp));        for(i=0,ans=0;i<n;i++){            for(j=A[i].a;j>0;j--){                if(dp[j]==0){                    dp[j]=A[i].b;                    break;                }            }            if(j==0) ans+=A[i].b;        }        printf("%d\n",ans);    }}

P题:这是一个很有意思的题目

题意:给我们一个串,只有大写字母,小写字母,我们可以按TAB键shift键来输入大些字母。让我们输入最少按多少次能把这个串用电脑敲出来,且最后TAB键得是关着的

分析:我们首先可以知道TAB键的作用,切换大小写,且shift键的作用是什么?我们知道小写的时候按住shift键再按字母可以变大写,大写的时候我们可以按住shift键再按字母可以变小写。那么这个时候是不是可以想到这样一种状态,TAB键关着的时候,TAB键开着的时候?dp[i+1][0]表示当前读取到第i个TAB键是关着的,dp[i+1][1]表示当前地区到第i个且TAB键是开着的。这样的话我们是不是可以找到相应的状态转移方程式呢?

首先我们找到第i个元素.

如果是大些字母的话

那么 dp[i+1][0]=min(dp[i][1]+2,dp[i][0]+2);

且 dp[i+1][1]=min(dp[i][1]+1,dp[i][0]+2);

如果是小些字母的话

那么 dp[i+1][0]=min(dp[i][1]+2,dp[i][0]+1);

且 dp[i+1][1]=min(dp[i][1]+2,dp[i][0]+2);

这样对吗?想想为什么这样

那么初始条件是什么呢?想想就知道了dp[0][0]=0,dp[0][1]=1(按了一下TAB键)。

最后输出的是什么呢?想想应该是min(dp[n][0],dp[n][1]+1);想想为什么要+1呢?

仔细想想这个题目是不是很有意思?

代码如下:

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int dp[105][2];char s[105];int main(void){    int i,j,len,T;    scanf("%d",&T);    while(T--){        scanf("%s",s);        len=strlen(s);        memset(dp,0,sizeof(dp));        dp[0][0]=0;dp[0][1]=1;        for(i=0;i<len;i++){            for(j=0;j<2;j++){                if(s[i]>='a'&&s[i]<='z'){                    if(j==0) dp[i+1][0]=min(dp[i][0]+1,dp[i][1]+2);                    else dp[i+1][1]=min(dp[i][0]+2,dp[i][1]+2);                }                else {                    if(j==1) dp[i+1][1]=min(dp[i][1]+1,dp[i][0]+2);                    else dp[i+1][0]=min(dp[i][1]+2,dp[i][0]+2);                }            }        }        printf("%d\n",min(dp[len][0],dp[len][1]+1));    }}


当你做完这套题时,再回首,看看原来的自己。

0 0