笔试遇到的动态规划题

发现笔试经常考查动态规划,代码量小但思维量大,我把遇到的题记录下来做个总结。这篇博客我打算一直更新,只要遇到dp就更。

题目 携程海洋馆的海豚小宝宝

携程海洋馆中有n只萌萌的小海豚,初始均为0岁,每只小海豚寿命是m岁,且这些小海豚会在birthYear[i]这些年份生产出一位宝宝海豚(1<=birthYear[i]<= m),每位宝宝海豚刚出生为0岁,问x年时海洋馆有多少只小海豚?
输入
第一行为n
第二行为m
第三行为p,表示生宝宝的年份数量
接下来p行,每行一个数表示海豚在该岁数将生宝宝
最后一行x,表示询问
输出
x年后总共有几只海豚
样例:
输入
5
5
2
2
4
5
输出
20

分析

看样例的意思,是岁数大于m的海豚才会死去。
很容易想到动态规划,因为第i年新生的宝宝数量一定是由i-birthYear[j]这些年份新生的宝宝产生的,那么不妨就设dp[i]为第i年新生的宝宝数量,最后计数是第n-m年到第n年的全部dp[i]求和。
其实初始n只的状况都是一样的,可以设dp[0]=1,最后的答案乘以n就行了。
代码就不写了。

题目 射击游戏

小仓喜欢射击,初始有N颗子弹,小仓自由选择k颗子弹进行射击,命中的概率为p[k],命中的话获得a[k]的分,然后可以使用剩下的子弹继续进行射击,没有命中的话今天的游戏结束,小仓能获得的得分最大期望是多少。
第一行为一个数N,表示子弹数量。
第二行N个数p[i],表示一次性用i颗子弹命中的概率。
第三行N个数a[i],表示一次性用i颗子弹命中以后可以获得的分数。
1<=N<=5000 0 <= p[i] <= 1 0<=a[i]<=1000
输出一个最高期望得分,保留两位小数。
样例如下:
输入1
2
0.8 0.5
1 2
输出1
1.44
输入2
3
0.9 0.1 0.1
2 1 1
输出2
4.88
解释:样例一中选择一颗子弹,如果命中再继续用一颗,期望是0.8*1+0.8*0.8*1 = 1.44,如果选两颗子弹一起射出,期望是0.5*2=1,所以最大期望是1.44;
样例二,如果直接用三颗,期望是0.1*1,如果先用两颗再用一颗,是0.1*1+0.1*0.9*2,如果先用一颗再用两颗,是0.9*2+0.9*0.1*1,一颗一颗又一颗,是0.9*2+0.9*0.9*2+0.9*0.9*0.9*2
请看题目三解答

题目 前缀和不能为负的方案数

有2N个数,其中有N个-1和N个1,问有多少种排列方法,使得任何前M个数加起来和不为负。
比如1,1,-1,-1符合要求;1,-1,-1,1不符合要求。
输入
一个数N(1<=N<=100)
输出
排列方案数
样例
输入
1
输出
1

分析

题目其实没有提到前缀和,但是看完题目应该能想到就是求前缀和不能为负的排列,那我就设当前位置为i,当前前缀和为j,显然有dp[i][j]=dp[i-1][j+1]+dp[i-1][j-1],也就是第i个数如果放-1,就是由前i-1个数前缀和为j+1递推过来的,如果放1,就是由前i-1个数前缀和为j-1递推而来。
注意到,当i小于等于N时,j显然右边界为i,当i大于N以后,j的右边届应该为2N-1,因为前缀和最大就是前N个都为1,前缀和为N,从第N+1个位置开始,前缀和必然开始变小。最后排列完成,前缀和为0。
结果返回dp[2*n]。

代码

#include
#include
#include
using namespace std;

int n, dp[210][110];
int main()
{
    cin >> n;
    memset(dp, 0, sizeof dp);
    dp[0][0] = 1;
    for (int i = 1; i <= n; i++){
        for (int j = 0; j <= i; j++){
            dp[i][j] = dp[i-1][j+1];
            if (j) dp[i][j] += dp[i-1][j-1];
        }
    }
    for (int i = n+1; i <= 2*n; i++){
        for (int j = 0; j <= 2*n-i; j++){
            dp[i][j] = dp[i-1][j+1];
            if (j) dp[i][j] += dp[i-1][j-1];
        }
    }
    /*for (int i = 1; i <= 2*n; i++){
        for (int j = 0; j <= n; j++)
            cout << dp[i][j] << " ";
        cout << endl;
    }*/
    cout << dp[2*n][0] << endl;
    return 0;
}

题目 钱老板打怪升级

钱老板在副本中打怪升级,这个副本中共有N个怪物,编号从0到N-1,每击杀一只怪物需要T秒的时间,编号为i的怪物在Li秒之后就会逃跑,被击杀后将获得Ei的经验值。
钱老板可以选择和任意一个还没逃跑的怪物战斗,选择怪物不需要花费时间,一旦进入战斗后怪物就无法逃跑,当所有怪物都逃跑或者被杀死后,副本结束。
请问钱老板最多能获得多少经验值。
输入
第一行N和T,表示N个怪物和击杀时间T;
接下来N行,每行两个整数Li和Ei,如题意。
1<=N<=100000
1<=T<=10000
1<=Li<=10000000
1<=Ei<=1000
输出
一个整数表示最多能获得的经验值
样例1
输入
3 2
1 2
2 5
3 4
输出
9
样例2
3 2
2 2
1 5
1 4
输出
7

分析

这题长得好像背包dp啊,每只怪物就好比一个重量为T价值为Ei的物品,那么背包的容量就应该是N*T,放置的时候需要考虑Li这个条件。
再仔细一想,重量都为T那不就是1,这样背包容量就是N啊。
有些细节要处理。

代码

#include
#include
#include
#include
using namespace std;

struct node{
	int L;
	int E;
}data[100010];
bool cmp(const node& x, const node& y){
	return x.L < y.L;
}
int N, T;
int dp[100100];
int main()
{
   cin>>N>>T;
   for (int i = 0; i < N; i++)
	cin >> data[i].L >> data[i].E;
   memset(dp, 1<<31, sizeof dp);
   dp[0] = 0;
   sort(data, data+N, cmp);
   for (int i = 0; i < N; i++){
	int cnt = data[i].L / T;
        cnt++;
	if (cnt > N) cnt = N;
	for (int j = cnt; j > 0; j--)
		dp[j] = max(dp[j], dp[j-1]+data[i].E);

   }
   int ans = 0;
   for (int i = 1; i <= N; i++) ans = max(ans, dp[i]);
   cout << ans << endl;
   return 0;
}

不知道后台数据是啥样的,如果T=1,,L都很大,是会超时的,不过也可以优化,当发现L/T+1>N的时候说明后面的这些怪物是不需要考虑时间问题的,全部加到ans中。

你可能感兴趣的:(C++笔试编程题)