动态规划(dp)总结

问题 T: 【动态规划】质数和分解

题目描述
任何大于1的自然数n,都可以写成若干个大于等于2且小于等于n的质数之和的形式(包括只有一个数构成的和表达式的情况),并且可能有不止一种质数和的形式。例如9的质数和表达式就有四种本质不同的形式:9=2+5+2=2+3+2+2=3+3+3=2+7。
这里所谓两个本质相同的表达式,是指可以通过交换其中一个表达式中参加和运算的各个数的位置而直接得到另一个表达式。试编程求解自然数n可以写成多少种本质不同的质数和的表达式。

输入

每一行存放一个自然数n(2≤n≤200)。

输出

输出自然数n的本质不同的质数和表达式的数目。

样例输入 Copy

2
200

样例输出 Copy

1
9845164

思路
完全背包
像这种求当前数可以表示成多少个已知数的和的问题,就使用完全背包

#include <iostream>
#include <cstdio>
using namespace std;
int prime[205],dp[205];
int tot=0;
void getprime()///素数打表
{
    for(int i=2;i<=200;i++)
    {
        int flag=1;
        for(int j=2;j*j<=i;j++)
            if(i%j==0) flag=0;
        if(flag) prime[tot++]=i;
    }
}
void getans()///完全背包,获取答案
{
    getprime();
    dp[0]=1;
    for(int j=0;j<tot;j++)
        for(int i=prime[j];i<=200;i++)
            dp[i]+=dp[i-prime[j]];
}
int main()
{
    getans();
    int n;
    while(~scanf("%d",&n))
        printf("%d\n",dp[n]);
}


问题 B: 【动态规划】圣诞树

题目
圣诞特别礼物挂在一棵圣诞树上,这棵树有n层,每层有一件礼物,每件礼物都有一个价值,有的礼物还有一些连接线,与下层的礼物相连。领取礼物的规则如下:任选一件礼物,它的下面如果有连接线,则可以继续取它连接的礼物,依此类推直至取到没有连接线的礼物才结束。你如果是第一个去取,怎样取才能获得最大的价值呢?请你编一程序解决这一问题。
输入
第1行只有一个数据n(n≤100),表示有n层礼物,以下有n行数据,分别表示第1~n层礼物的状态,每行至少由一个数据构成,且第一个数据表示该礼物的价值,后面的数据表示它与哪些层的礼物相连,如果每行只有一个数据则说明这层礼物没有与下层礼物相连,每个数据大小均不超过10000。
输出
只有一个数,表示获得的最大价值。
思路
动态转移方程是:dp[i]=max(a[i],a[i]+dp[j])(j为与i相连的层数)因为礼物是上层连下层,所以从下层开始求最大值,这样可以保证每一次求得的都是最大值,然后记忆化搜索即可
注意
读入比较麻烦,根据每个数后面是空格还是回车判断是否继续输入即可

#include <iostream>
#include <vector>
#include <cstdio>
using namespace std;
int dp[105];
int maxn=0;
vector<int> visit[105];
int a[105];
int slove(int x)
{
    if(visit[x].size()==0) return dp[x]=a[x];///没有预其相连的,dp[x]值为a[x],返回
    if(dp[x]) return dp[x];///记忆化,防止重复搜索
    for(int i=0;i<visit[x].size();i++)
    {
        int next=visit[x][i];///获取下一个节点
        dp[x]=max(dp[x],slove(next)+a[x]);///dp[x]=max(dp[x],dp[next]+a[x]);
    }
    return dp[x];
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        char ch=getchar();
        if(ch==' ')
        {
            do
            {
                int x;
                scanf("%d",&x);
                visit[i].push_back(x);
                ch=getchar();
            }while(ch==' ');
        }
    }
    for(int i=n;i>=1;i--) maxn=max(maxn,slove(i));
    cout<<maxn;
    return 0;
}

问题 C: 传球游戏

题目
上体育课时,墨老师经常带着同学们一起做游戏。这次,墨老师带着同学们一起做传球游戏,游戏规则是这样的:N个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止,此时拿着球没传出去的那个同学就是败者,要给大家表演一个节目。
聪明的张琪曼提出一个有趣的问题:有多少种不同的传球方法可以使得从张琪曼手里开始传的球,传了M次以后,又回到张琪曼手里。两种传球的方法被称作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有3个同学1号、2号、3号,并假设张琪曼为1号,球传了3次回到张琪曼手里的方式有1à2à3à1和1à3à2à1,共两种。
输入
有两个用空格隔开的整数N,M(3≤N≤30,1≤M≤30)。
输出
有一个整数,表示符合题目的方法数。
思路
1.第一种方法:使用排列组合求;
1)本题可以转化为求:有m个数相加(这些数只能为1或-1),求有多少种情况使得他们的和为n的倍数(1,-1排的位置不同,则情况不同)
2)那么假设有i个1,j个-1,则abs(i-j)%n==0时,和是符合条件的,然后再将这m个数去排列,可知共有C(m,j)种情况;
3)然后把所有符合条件的情况加起来就是答案了
代码1

#include <iostream>
#include <cmath>
using namespace std;
long long c[35][35];
inline void getC()
{
    for(int i=1;i<=30;i++) c[i][0]=c[0][i]=c[i][i]=1;
    for(int i=2;i<=30;i++)
        for(int j=1;j<=30;j++)
            c[i][j]=c[i-1][j-1]+c[i-1][j];
}
int main()
{
    getC();
    int n,m;cin>>n>>m;
    long long ans=0;
    for(int i=0;i<=m;i++)
    {
        int j=m-i;
        if((int)abs(i-j)%n==0) ans+=c[m][i];
    }
    cout<<ans<<endl;
    return 0;
}

2.使用记忆化搜索
1)dp[i][j]代表传了j次球传到第i个人的情况数,那么我们可以知道dp[i][j]=dp[(i+1)%n][j-1]+dp[(i-1+n)%n][j-1](即传了j-1次传到第i-1个人情况数与传了j-1次传到第i+1个人的情况数的和)

#include <iostream>
#include <cmath>
#include <cstring>
using namespace std;

long int dp[35][35];
int n,m;
long int solve(int p,int t)
{
    if(t==1&&(p==2||p==0)) ///递归边界,若次数还剩一次时,传到了第2个人或第n个人,则dp[p][t]=1
        return dp[p][t]=1;
    if(t==1) ///若次数剩一次时,球没传到第1个人的两边,则dp[p][t]=0
        return dp[p][t]=0;
    if(dp[p][t]!=-1) ///已经得到答案
        return dp[p][t];
    return dp[p][t]=solve((p+1)%n,t-1)+solve((p-1+n)%n,t-1);
}
int main()
{
    memset(dp,-1,sizeof(dp));
    cin>>n>>m;
    cout<<solve(1,m)<<endl;
}

问题 E: 【动态规划】抢金块

题目
地面上有一些格子,每个格子上面都有金块,但不同格子上的金块有不同的价值,你一次可以跳S至T步(2≤S<T≤10)。例如S=2,T=4,你就可以跳2步、3步或4步。你从第一个格子起跳,必须跳到最后一个格子上,请你输出最多可以获得的金块的总价值。
输入
第1行是格子个数n (n<1000);
第2行是S和T,保证T大于s(2≤S<T≤10);
第3行是每个格子上的金块价值Pi (Pi<10000)。
输出
输出最多可以获得的金块的总价值。
思路
记忆化搜索,dp[pos]=max(dp[pos],dp[pos-i]+w[pos]);

#include <iostream>

using namespace std;
int n,s,t;
int dp[1005];
int w[1005];
int solve(int pos)
{
    if(pos<=0) return -1;///无法到达,返回-1,而不是0
    if(dp[pos]) return dp[pos];
    if(pos==1)  return dp[pos]=w[1];
    for(int i=s;i<=t;i++)
    {
        int temp=solve(pos-i);
        if(temp!=-1)dp[pos]=max(temp+w[pos],dp[pos]);
    }
    if(dp[pos]==0) dp[pos]=-1;
    return dp[pos];
}
int main()
{
    cin>>n>>s>>t;
    for(int i=1;i<=n;i++) cin>>w[i];
    cout<<solve(n)<<endl;
    return 0;
}


问题 G: 【动态规划】攀登宝塔

题目:
有一天,贝贝做了一个奇怪的梦,梦中他来到一处宝塔,他想要从塔的外面爬上去。这座宝塔的建造特别,塔总共有n层,但是每层的高度却不相同,这造成了贝贝爬过每层的时间也不同。贝贝会用仙术,每用一次可以让他向上跳一层或两层,这时不会耗费时间,但是每次跳跃后贝贝都将用完灵力,必须爬过至少一层才能再次跳跃。贝贝想用最短的时间爬到塔顶,可是他找不到时间最短的方案,所以请你帮他找一个时间最短的方案,让他爬到塔顶(可以超过塔高)。贝贝只关心时间,所以你只要告诉他最短时间是多少就可以了。
输入
第1行一个数n (n≤10000),表示塔的层数。
接下来的n行每行一个不超过100的正整数,表示从下往上每层的所需的时间。
输出
一个数,表示最短时间。
思路
dp[0][j]表示第j层不使用法力所需要的最小路程,
dp[1][j]表示第j层使用法力所需要的最小路程
那么可以得到状态转移方程 :
1)dp[0][j]=min(dp[0][j-1],dp[1][j-1])+a[i],因为第j层用爬的,那么就等于到达第j-1层的最小路程(在第j-1层用法力和不用法力中选小的)加上a[i];
2)dp[1][j]=min(dp[0][j-1],dp[0][j-1]);因为使用法力可以跳1到2层,所以,dp[1][[j]为第j-1和j-2层中不使用法力跳少的;

#include <iostream>

using namespace std;
int n;
int a[10005];
int dp[2][10005];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=2;i<=n;i++)
    {
        dp[0][i]=min(dp[1][i-1],dp[0][i-1])+a[i];
        dp[1][i]=min(dp[0][i-1],dp[0][i-2]);
    }
    cout << min(dp[0][n],dp[1][n]) << endl;
    return 0;
}

问题 R: 【动态规划】货币系统

题目:
母牛们不但创建了它们自己的政府,而且选择建立了自己的货币系统。它们对货币的数值感到好奇。传统地,一个货币系统是由1,5,10,20或25,50,100的单位面值组成的。母牛想知道用货币系统中的货币来构造一个确定的面值,有多少种不同的方法。
举例来说,使用一个货币系统{1,2,5,10,…}产生18单位面值的一些可能的方法是:18×1,9×2,8×2+2×1,3×5+2+1等等。写一个程序,计算用给定的货币系统来构造一个确定的面值有多少种方法。
输入
第1行有两个整数V,n,其中v(1≤V≤25)表示货币系统中货币的种类,n是要构造的面值(1≤n≤10000);
第2~v+l行:表示可用的货币面值(每行一个)。
输出
输出方案总数。
思路
完全背包,所以j从1到target,dp[j]=dp[j]+dp[j-a[i]],答案dp[j]的情况数等于当前答案dp[j]加上前面所有能通过再使用一张纸币达到j的情况数(即dp[j-a[i]] (i from 1 to n) )

#include <iostream>
using namespace std;
int n,target;
long long dp[10005];
int w[30];
int main()
{
    cin>>n>>target;
    for(int i=1;i<=n;i++) cin>>w[i];
    dp[0]=1;
    for(int i=1;i<=n;i++)
        for(int j=w[i];j<=target;j++)
            dp[j]+=dp[j-w[i]];
    cout << dp[target] << endl;
    return 0;
}

问题 S: 【动态规划】金银岛

题目
在金银岛上,人们使用的货币的值都是完全平方数,例如l,4,9,…,289。支付十元钱有下列四种方法:
(1)十个一元的钱; (2)一个四元的,六个一元的;
(3)两个四元的,两个一元的; (4)一个九元的,一个一元的。
你的任务是对于给定的钱数(设其值少于300),求出支付方法的总数。
输入
输入共有n+l行(n未知),以数字0结束,每行为一个自然数t(1≤t≤300)。
输出
输出共有n行,每行表示对于给定的钱数t,对应的支付方案总数。
思路
与上一题货币系统类似,只要只是能花的钱变成i*i

#include <iostream>

using namespace std;
int n,target;
long long dp[10005];
int w[30];
int main()
{
    cin>>n>>target;
    for(int i=1;i<=n;i++) cin>>w[i];
    dp[0]=1;
    for(int i=1;i<=n;i++)
        for(int j=w[i];j<=target;j++)
            dp[j]+=dp[j-w[i]];
    cout << dp[target] << endl;
    return 0;
}

问题 U: 【动态规划】最长公共子序列

题目:
一个给定的子序列是在该序列中删去若干元素后得到的序列。例如,序列z=<B,C,D,B> 是序列X=<A,B,C,B,D,A,B>的子序列;
给定两个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X,Y的公共子序列。
例如,若X=<A,B,C,B,D,A,B>,Y=<B,D,C,A,B,A>,
那么:<B,C,A>是X和Y的一个公共子序列,<B,C,B,A>也是X和Y的一个公共子序列;
编程求出给定的两个序列中,最长公共子序列的长度。
输入
共两行,各一个字符串,第一个字符串表示第一个序列,第二个字符串表示第二个序列,两个字符串长度均小于1000。
输出
一个整数,即两个序列的公共子序列的长度。
思路
LCS算法:
dp[i][j]代表字符串a前i个字母和字符串b前j个字母的最长公共子序列长度
那么从最后一个字母开始考虑如果
1)若a[lena-1]==b[lenb-1]:dp[lena][lenb]=dp[lena-1][lenb-1]+1;则当前的答案等于每个字符串都往前一个的答案+1;
2)若a[lena-1]!=b[lenb-1]: dp[lena][lenb]=max(dp[lena-1][lenb],dp[lena][lenb-1])

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int dp[1005][1005];
int main()
{
    char a[1005],b[1005];
    scanf("%s%s",a,b);
    int l1=strlen(a),l2=strlen(b);
    for(int i=1;i<=l1;i++)
    {
        for(int j=1;j<=l2;j++)
        {
            if(a[i-1]==b[j-1])
                dp[i][j]=dp[i-1][j-1]+1;
            else
                dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
        }
    }
    cout << dp[l1][l2]<< endl;
    return 0;
}

递归写法:

#include <iostream>
#include <cstring>
#include <cstring>
using namespace std;

char a[1005],b[1005];
int dp[1005][1005];
int solve(int l1,int l2)
{
    if(dp[l1][l2]!=-1) return dp[l1][l2];//记忆化搜索
    if(l1==0||l2==0) return dp[l1][l2]=0;//递归边界,空串,返回0
    if(a[l1]==b[l2])  ///若a[l1]==a[l2]则答案+1,往两个字符串-1的方向搜索
    	dp[l1][l2]=max(dp[l1][l2],solve(l1-1,l2-1)+1);
    else  ///若不等,则选择两个子问题(a[l1-1]或b[l2-1])中比较大的那个
    	dp[l1][l2]=max(dp[l1][l2],max(solve(l1-1,l2),solve(l1,l2-1)));
    return dp[l1][l2];
}

int main()
{
    memset(dp,-1,sizeof(dp));
    cin>>a+1>>b+1;///注意从a[1],b[1]开始读入
    cout<<solve(strlen(a+1),strlen(b+1))<<endl;
    return 0;
}

问题 F: 【动态规划】维修栅栏

题目
农场的栅栏年久失修,出现了多处破损,晶晶准备维修它,栅栏是由n块木板组成的,每块木板可能已经损坏也可能没有损坏。晶晶知道,维修连续m个木板(这m个木板不一定都是损坏的)的费用是sqrt(m)。可是,怎样设计方案才能使总费用最低呢?请你也来帮帮忙。
输入
第1行包含一个整数n(n≤2500),表示栅栏的长度;
第2行包含n个由空格分开的整数(长整型范围内)。如果第i个数字是0,则表示第i块木板已经损坏,否则表示没有损坏。
输出
仅包含一个实数,表示最小维修费用;注意:答案精确到小数点后3位。
思路
!!!!!!!!!!!!!!!!!!!!!!!!!
dp[i]=min(dp[i],dp[j]+sqrt(i-j) ):dp[i]等于第j个的dp[j]值加上sqrt(i-j)中最小的

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

long long a[2505];
double dp[2505];
int main()
{
    int n;cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    dp[0]=0;
    for(int i=1;i<=n;i++)
    {
        dp[i]=n;
        if(a[i]) dp[i]=dp[i-1];///不为0,就和前一个的最小值相同
        for(int j=0;j<i;j++)
            dp[i]=min(dp[i],dp[j]+sqrt(i-j));///更新最小值
    }
    printf("%.3lf",dp[n]);
    return 0;
}

题目
求最长不递降子序列长度
注意
因为他没有固定数组大小,所以读入数组时,要按如下方式读入,while(~scanf("%d",&a[++n]))是不行的,wa了4发,还有dp数组要初始化为1;

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;
int dp[4005];
long int a[4005];
int main()
{
    int n=0,x;
    while(scanf("%d",&x)!=EOF) a[++n]=x,dp[n]=1;
    for(int i=2;i<=n;i++)
        for(int j=i-1;j>=1;j--)
            if(a[j] >= a[i]) dp[i]=max(dp[i],dp[j]+1);
    int maxn=0;
    for(int i=1;i<=n;i++) maxn=max(maxn,dp[i]);
    cout<<maxn<<endl;
}

问题 M: 【动态规划】合唱队形

题目:
n位同学站成一排,音乐老师要请其中的(n-K)位同学出列,使得剩下的K位同学排成合唱队形。合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2,…,K,他们的身高分别为T1,T2,…,TK,则他们的身高满足T1<…Ti+l>…>TK(1≤i≤K)。你的任务是:已知所有n位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入
第1行是一个整数n(2≤n≤100),表示同学的总数。
第2行有n个整数,用空格分隔,第i个整数Ti (130≤Ti≤230)是第i位同学的身高(厘米)。
输出
一个整数,就是最少需要几位同学出列。
思路
就是求两个最长递升子列的长度,答案就是dp1[i]+dp2[i]-1中最大的
注意
不要想太多,i的范围是1到n,两头也要考虑,也即他可以一直递升

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;

int n,a[105],dp1[105],dp2[105];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i],dp1[i]=dp2[i]=1;
    for(int i=1;i<=n;i++)  for(int j=1;j<i;j++)  if(a[j]<a[i])dp2[i]=max(dp2[i],dp2[j]+1);
    reverse(a+1,a+n+1);
    for(int i=1;i<=n;i++)  for(int j=1;j<i;j++)  if(a[j]<a[i])dp1[i]=max(dp1[i],dp1[j]+1);
    reverse(dp1+1,dp1+n+1);
    int maxn=0;
    for(int i=1;i<=n;i++)///i可以取两头
    	maxn=max(maxn,dp1[i]+dp2[i]-1);
    cout<<n-maxn<<endl;
}

问题 W: 【区间DP】能量项链

题目:
每个天顶星人都随身佩戴着一串能量项链,在项链上有N颗能量珠。能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。因为只有这样,通过吸盘(吸盘是天顶星人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。如果前一颗能量珠的头标记为m,尾标记为r,后一颗能量珠的头标记为r,尾标记为n,则聚合后释放的能量为m×r×n(天顶星人计量单位),新产生的珠子的头标记为m,尾标记为n。
需要时,天顶星人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。
例如:设N=4,4颗珠子的头标记与尾标记依次为(2,3) (3,5) (5,10) (10,2)。我们用记号⊕表示两颗珠子的聚合操作,(j⊕k)表示第j,k两颗珠子聚合后所释放的能量。则第4、1两颗珠子聚合后释放的能量为:(4⊕1)=10×2×3=60。
这一串项链可以得到最优值的一个聚合顺序所释放的总能量为
(4⊕1)⊕2)⊕3)=10×2×3+10×3×5+10×5×10=710。
输入
第一行是一个正整数N(4≤N≤100),表示项链上珠子的个数。第二行是N个用空格隔开的正整数,所有的数均不超过1000。第i个数为第i颗珠子的头标记(1≤i≤N),当i<N时,第i颗珠子的尾标记应该等于第i+1颗珠子的头标记。第N颗珠子的尾标记应该等于第1颗珠子的头标记。
至于珠子的顺序,你可以这样确定:将项链放到桌面上,不要出现交叉,随意指定第一颗珠子,然后按顺时针方向确定其他珠子的顺序。
输出
输出只有一行,是一个正整数E(E≤2.1*109),为一个最优聚合顺序所释放的总能量。
思路
区间dp,合并区间时,加上的是a[i]*a[k+1]*a[j+1]

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;

int n,a[205],dp[205][205];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i],a[n+i]=a[i];
    for(int l=2;l<=n;l++)
        for(int i=1;i+l-1<2*n;i++)///注意区间终点要到2*n-1,即i+l-1<2*n,不能写i<n
        {
            int j=i+l-1;
            for(int k=i;k<j;k++)
                dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+a[i]*a[k+1]*a[j+1]);
        }
    int maxn=0;
    for(int i=1;i<=n;i++) maxn=max(maxn,dp[i][i+n-1]);
    cout<<maxn<<endl;
}

问题 X: 【动态规划】款待奶牛

题目:
FJ有n(1≤n≤2000)个美味的食物,他想卖掉它们来赚钱给奶牛。这些食物放在一些箱子里,它们有些有趣的特性:
(1)这些食物被编号为1~n,每一天FJ可以从这排箱子的头部或者尾部取出食物去卖;
(2)这些食物放得越久,年龄越大,价值越大,食物i有一个初始的价值V(i);
(3)放了a天后,年龄为a,食物最终的价值为V(i)×a。
给定每一个食物的初始价值v(i),请求出FJ卖掉它们后可以获得的最大价值,第一天出售的食物的年龄为1,此后每增加一天食物的年龄就增加1。
输入
第1行:一个整数n;
第i+l行:每行为食物i的初始价值V(i)。
输出
1行:FJ最终可以获得的最大价值。
思路
dp[j][l]代表以j为起点长度为l的最优解(注意:l表示最后l天的决策)
状态转移方程:
dp[j][l]=max(dp[j][l-1]+a[j+l-1]*(n-l+1),dp[j+1][l-1]+(n-l+1)*a[j]);

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;

int n,dp[2005][2005];
int a[2005];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int l=1;l<=n;l++)
        for(int j=1;j+l-1<=n;j++)
            dp[j][l]=max(dp[j][l-1]+a[j+l-1]*(n-l+1),dp[j+1][l-1]+(n-l+1)*a[j]);
    cout<<dp[1][n]<<endl;
}

问题 D: 【动态规划】黑熊过河

题目
晶晶的爸爸给晶晶出了一道难题:有一只黑熊想过河,但河很宽,黑熊不会游泳,只能借助河面上的石墩跳过去,它可以一次跳一墩,也可以一次跳两墩,但是每跳一次都会耗费一定的能量,黑熊最终可能因能量不够而掉入水中。所幸的是,有些石墩上放了一些食物,这些食物可以给黑熊增加一定的能量。问黑熊能否利用这些石墩安全地抵达对岸?请计算出抵达对岸后剩余能量的最大值。
输入
第1行包含两个整数P(黑熊的初始能量),Q(黑熊每次起跳时耗费的能量),0≤P,Q≤1000;
第2行只有一个整数n(1≤n≤106),即河中石墩的数目;
第3行有n个整数,即每个石墩上食物的能量值ai(0≤ai≤1000)。
输出
仅1行,若黑熊能抵达对岸,输出抵达对岸后剩余能量的最大值;若不能,则输出“NO”。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

int dp[1000005],n;
int v[1000005];
int main()
{
    int use;
    cin>>dp[0]>>use;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>v[i];
    if(dp[0]>=use) dp[1]=dp[0]-use+v[1];
    for(int i=2;i<=n+1;i++)
    {
        int sign=1;
        ///>=要记得带等号
        if(dp[i-1]>=use)dp[i]=dp[i-1]-use+v[i],sign=0;
        if(dp[i-2]>=use)dp[i]=max(dp[i],dp[i-2]-use+v[i]),sign=0;
        if(sign)///两个都不可到达,直接输出,主要是这里的判断
        {
            cout<<"NO"<<endl;
            return 0;
        }
    }
    cout<<dp[n+1]<<endl;
}

问题 I: 【动态规划】桐桐的爬山计划

题目:
桐桐一直有个梦想,很希望像“蜘蛛人”罗伯特一样飞檐走壁。为了达成这个梦想,桐桐每天都辛勤练习攀爬。练习的出发点与终点都是在地上面。给出一个数列,代表她每次移动的距离。这个移动可以向上,也可以向下。但是不可能到达地下面去的。而她做练习使用的建筑物总是比她到达过的最高位置高2米。现在我们希望这个建筑物的高度越小越好。
如:20 20 20 20
如果是上,上,下,下的话,这个建筑物就要42米高,如果是上,下,上,下,就只要22米高。
当然有些数列是无解的,例如:3421645。
输入
第1行输入n(n≤l00),代表有n个爬行距离;
第2行输入n个爬行距离(均为整数),这些爬行距离的总和不超过10000。
输出
如果有解,则输出最小的高度;
否则输出’IMPOSSIBLE’。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>
#define rep(i,j,n) for(register int i=j;i<=n;i++)
using namespace std;

int n,dp[105][10005],v[105],sum[105];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)cin>>v[i],sum[i]=sum[i-1]+v[i];
    dp[1][sum[1]]=sum[1];
    rep(i,2,n)
    {
        rep(j,0,sum[i])
        {
            if ((j - v[i] < 0 || !dp[i - 1][max(0, j - v[i])]) &&(j + v[i] > sum[i] || !dp[i - 1][min(sum[i], j + v[i])]))
                continue;
            else if (j - v[i] < 0 ||! dp[i - 1][max(0, j - v[i])])
                dp[i][j] = max(j, dp[i - 1][min(sum[i], j + v[i])]);
            else if (j + v[i] > sum[i] ||!dp[i - 1][min(sum[i], j + v[i])] )
                dp[i][j] = max(j, dp[i - 1][max(0, j - v[i])]);
            else
                dp[i][j] = max(j, min(dp[i - 1][max(0, j - v[i])], dp[i - 1][min(sum[i], j + v[i])]));
        }
    }
    if (dp[n][0])
        cout << dp[n][0] + 2 << endl;
    else
        cout << "IMPOSSIBLE" << endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Spidy_harker/article/details/101010977