1. 合唱团

有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?

输入描述:
每个输入包含 1 个测试用例。每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50)。
接下来的一行包含两个整数,k 和 d (1 <= k <= 10, 1 <= d <= 50)。
输出描述:
输出一行表示最大的乘积。
输入例子1:
3
7 4 7
2 50
输出例子1:
49

思路:想到了用dp来解这题,但是接下来建立dp模型时却遇到了很多困难,尝试用 dp[n][k] 这样的形式来代表从n个学生中选取k个,那么其子问题便可以用 dp[]n-1[k-1] 这种形式来表示,对于第n个学生可以选择也可以不选择。结果陷入了思维困区中,最终的也只能解决60%的case。究其原因在于这题有个限制条件d,以及能力值有负数的情况,所以如何建立dp模型使得满足这些限定条件的同时问题的分析能够变得思维清晰才是关键。

看了下解析,首先明确的是可以使用dp来解决这题,因为1.求解的是最优化问题;2.可以分解为最优子结构。

问题的分解:

从n个学生中,选择k个,可以看成是:先从n个学生里选择最后1个,然后在剩下的里选择k-1个,并且让这1个和前k-1个满足约束条件

(注意和我之前思路的不同,这个是从n个学生中选取最后一个,我之前想的是对于最后一个学生(比如第n个学生)要么选择他要么不选。)

数学描述:

为了得到递推公式,需要建立数学模型。记第k个人的位置为one,则可以用f[one][k]表示从n个人中选择k个的方案。然后,它的子问题,需要从one前面的left个人里面,选择k-1个,这里left表示k-1个人中最后一个(即第k-1个)人的位置,因此,子问题可以表示成f[left][k-1].

学生能力数组记为arr[n+1],第 i 个学生的能力值为arr[i]
one表示最后一个人,其取值范围为[1,n];
left表示第k-1个人所处的位置,需要和第k个人的位置差不超过d,因此
max{k-1,one-d}<=left<=one-1

上面有点疑惑的是 max{k-1,one-d}<=left<=one-1 这里,为什么要在 k-1 和 one-d 中取较大的呢?因为当one-d是有可能小于0的,即选择第一个(或者前几个)时应该会到最左边,这时候left的位置要保证的一个条件是他左边剩下的人数还够选,比如 从6个人中选3个,如果one的位置在第3个人,那么下一个选的位置只能是第2个人,否则前面剩下的人数不够选了。换而言之就是说在选择left的位置是,需要保证 left >= k-1, 即从第1个到第left总共有left个人,这些人数最起码等于我们剩下要选的人数 k-1。并且按照这个准则,one也是要大于或等于k的。

在n和k定了之后,需要求解出n个学生选择k个能力值乘积的最大值。因为能力值有正有负,所以

当one对应的学生能力值为正时,
f[one][k] = max{f[left][k-1]*arr[i]},  (min{k-1,one-d}<=left<=one-1);
当one对应的学生能力值为负时
f[one][k] = max{g[left][k-1]*arr[i]},  (min{k-1,one-d}<=left<=one-1);
此处g[][]是存储n个选k个能力值乘积的最小值数组

代码:

import java.util.Scanner;

public class Main_jrh_AC {
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
while(sc.hasNext()) {
//总人数
int n = sc.nextInt();
//学生能力值数组,第i个人直接对应arr[i]
int[] arr = new int[n + 1];
//初始化
for (int i = 1; i <= n; i++) {//人直接对应坐标
arr[i] = sc.nextInt();
}
//选择的学生数
int kk = sc.nextInt();
//间距
int dd = sc.nextInt(); /**
* 递推的时候,以f[one][k]的形式表示
* 其中:one表示最后一个人的位置,k为包括这个人,一共有k个人
* 原问题和子问题的关系:f[one][k]=max{f[left][k-1]*arr[one],g[left][k-1]*arr[one]}
*/
//规划数组
long[][] f = new long[n + 1][kk + 1];//人直接对应坐标,n和kk都要+1
long[][] g = new long[n + 1][kk + 1];
//初始化k=1的情况
for(int one = 1;one<=n;one++){
f[one][1] = arr[one];
g[one][1] = arr[one];
}
//自底向上递推
for(int k=2;k<=kk;k++){
for(int one = k;one<=n;one++){
//求解当one和k定的时候,最大的分割点
long tempmax = Long.MIN_VALUE;
long tempmin = Long.MAX_VALUE;
for(int left = Math.max(k-1,one-dd);left<=one-1;left++){
if(tempmax<Math.max(f[left][k-1]*arr[one],g[left][k-1]*arr[one])){
tempmax=Math.max(f[left][k-1]*arr[one],g[left][k-1]*arr[one]);
}
if(tempmin>Math.min(f[left][k-1]*arr[one],g[left][k-1]*arr[one])){
tempmin=Math.min(f[left][k-1]*arr[one],g[left][k-1]*arr[one]);
}
}
f[one][k] = tempmax;
g[one][k] = tempmin;
}
}
//n选k最大的需要从最后一个最大的位置选
long result = Long.MIN_VALUE;
for(int one = kk;one<=n;one++){
if(result<f[one][kk]){
result = f[one][kk];
}
}
System.out.println(result);
}
}
}

复制了解析的代码,发现自己的思路还是太死了,这题虽然是dp,但是和典型的dp解法并不相同。自己在考虑这题的时候,一是没有分析出比如 left = Math.max(k-1,one-dd) 这样的边界条件,而是没有搞清楚具体怎么从低向上计算,三是拘泥于以前的dp算法,没有考虑到维护两个dp数组,并且坚定认为dp算法最后返回的就应该是 dp[n][k] 这种形式,总认为dp 数组计算完了就能直接得出最优解。而这题是 dp 计算完了后,还遍历了这个数组一遍,因为按照开始的dp定义,dp[n][k] 未必是题目要求的最优解。还有一点值学习的是上面的 “人直接对应坐标” 这个做法,自己在做这题时非得下标0开始,结果在计算时还得考虑下标和第几个人能不能对应的上,最后直接导致大脑爆炸。最后要注意的是取值范围,因为乘积可能很大,所以应该使用long类型而不是int。

2. 地牢逃脱

给定一个 n 行 m 列的地牢,其中 '.' 表示可以通行的位置,'X' 表示不可通行的障碍,若从 (x0 , y0 ) 位置出发,遍历这个地牢,和一般的游戏所不同的是,他每一步只能按照一些指定的步长遍历地牢,要求每一步都不可以超过地牢的边界,也不能到达障碍上。地牢的出口可能在任意某个可以通行的位置上。求在最坏情况下,需要多少步才可以离开这个地牢。 

输入描述:

每个输入包含 1 个测试用例。每个测试用例的第一行包含两个整数 n 和 m(1 <= n, m <= 50),表示地牢的长和宽。接下来的 n 行,每行 m 个字符,描述地牢,地牢将至少包含两个 '.'。
接下来的一行,包含两个整数 x0, y0,表示牛牛的出发位置(0 <= x0 < n, 0 <= y0 < m,左上角的坐标为 (0, 0),出发位置一定是 '.')。
之后的一行包含一个整数 k(0 < k <= 50)表示合法的步长数,接下来的 k 行,每行两个整数 dx, dy 表示每次可选择移动的行和列步长(-50 <= dx, dy <= 50)

输出描述:

输出一行一个数字表示最坏情况下需要多少次移动可以离开地牢,如果永远无法离开,输出 -1。
以下测试用例中,可以上下左右移动,在所有可通行的位置.上,地牢出口如果被设置在右下角,想离开需要移动的次数最多,为3次。

输入例子1:

3 3
...
...
...
0 1
4
1 0
0 1
-1 0
0 -1

输出例子1:

3

思路

这题有一点没有讲清楚,那就是题目中的移动其实是“跳跃”,就是说从1到3,不是从1走到2再走到3而是直接跳到3。想到用BFS来解题了,因为一看就是比较典型的BFS问题。但是还有一些细节上的问题可以学习下答案的代码。

import java.util.*;

public class Main {
public static void main(String[] args){
Scanner in = new Scanner(System.in); while (in.hasNext()) {
int x=in.nextInt();
int y=in.nextInt(); char[][] points=new char[x][y];
int[][] tar=new int[x][y];
for(int i=0;i<x;i++){
String str=in.next();
points[i]=str.toCharArray();
}
int startx=in.nextInt();
int starty=in.nextInt();
int k=in.nextInt();
int[] stepx=new int[k];
int[] stepy=new int[k];
for(int i=0;i<k;i++){
stepx[i]=in.nextInt();
stepy[i]=in.nextInt();
}           // 下面用了两个队列来分别存x轴位置和y轴位置,之前自己做的时候只用了一个队列,结果发现队列元素的类型不是很好选择
Queue<Integer> xqueue=new LinkedList<Integer>();
Queue<Integer> yqueue=new LinkedList<Integer>(); xqueue.add(startx);
yqueue.add(starty); tar[startx][starty]=1; //访问标记这块还是很有意思的,设置成整数而不是boolean型是为了后面取最大距离(步长)
while(!xqueue.isEmpty()&&!yqueue.isEmpty()){
startx=xqueue.remove();
starty=yqueue.remove();
for(int i=0;i<k;i++){
if(startx+stepx[i]<x&&startx+stepx[i]>=0&&starty+stepy[i]<y&&starty+stepy[i]>=0)
if(tar[startx+stepx[i]][starty+stepy[i]]==0){
if(points[startx+stepx[i]][starty+stepy[i]]=='.'){
tar[startx+stepx[i]][starty+stepy[i]]=tar[startx][starty]+1;
xqueue.add(startx+stepx[i]);
yqueue.add(starty+stepy[i]);
}
else
tar[startx+stepx[i]][starty+stepy[i]]=-1; //访问点为X
}
}
}
int max=0;
int getRoad=1;
for(int i=0;i<x;i++)
for(int j=0;j<y;j++){
if(points[i][j]=='.'&&tar[i][j]==0){
getRoad=0; //有存在没有被访问的“.”说明不能遍历完全,有些出口到不了。
}
max=Math.max(max, tar[i][j]); // 如果所有的'.'都被访问了直接去步长最长的那个
}
if(getRoad==0)
System.out.println(-1);
else
System.out.println(max-1); }
}
}

两个值得借鉴的地方:两个队列存储以及步长矩阵,尤其是那个步长矩阵。其实对于循环处理问题的计算,全部计算好后再去分析比每次循环都去分析要容易考虑得多。

[编程题] 合唱团 && 地闹逃脱的更多相关文章

  1. 算法是什么我记不住,But i do it my way. 解一道滴滴出行秋招编程题。

    只因在今日头条刷到一篇文章,我就这样伤害我自己,手贱. 刷头条看到一篇文章写的滴滴出行2017秋招编程题,后来发现原文在这里http://www.cnblogs.com/SHERO-Vae/p/588 ...

  2. C算法编程题系列

    我的编程开始(C) C算法编程题(一)扑克牌发牌 C算法编程题(二)正螺旋 C算法编程题(三)画表格 C算法编程题(四)上三角 C算法编程题(五)“E”的变换 C算法编程题(六)串的处理 C算法编程题 ...

  3. C算法编程题(七)购物

    前言 上一篇<C算法编程题(六)串的处理> 有些朋友看过我写的这个算法编程题系列,都说你写的不是什么算法,也不是什么C++,大家也给我提出用一些C++特性去实现问题更方便些,在这里谢谢大家 ...

  4. C算法编程题(六)串的处理

    前言 上一篇<C算法编程题(五)“E”的变换> 连续写了几篇有关图形输出的编程题,今天说下有关字符串的处理. 程序描述 在实际的开发工作中,对字符串的处理是最常见的编程任务.本题目即是要求 ...

  5. C算法编程题(五)“E”的变换

    前言 上一篇<C算法编程题(四)上三角> 插几句话,说说最近自己的状态,人家都说程序员经常失眠什么的,但是这几个月来,我从没有失眠过,当然是过了分手那段时期.每天的工作很忙,一个任务接一个 ...

  6. C算法编程题(四)上三角

    前言 上一篇<C算法编程题(三)画表格> 上几篇说的都是根据要求输出一些字符.图案等,今天就再说一个“上三角”,有点类似于第二篇说的正螺旋,输出的字符少了,但是逻辑稍微复杂了点. 程序描述 ...

  7. C算法编程题(三)画表格

    前言 上一篇<C算法编程题(二)正螺旋> 写东西前还是喜欢吐槽点东西,要不然写的真还没意思,一直的想法是在博客园把自己上学和工作时候整理的东西写出来和大家分享,就像前面写的<T-Sq ...

  8. C算法编程题(二)正螺旋

    前言 上一篇<C算法编程题(一)扑克牌发牌> 写东西前总是喜欢吐槽一些东西,还是多啰嗦几句吧,早上看了一篇博文<谈谈外企涨工资那些事>,里面楼主讲到外企公司包含的五类人,其实不 ...

  9. C算法编程题(一)扑克牌发牌

    前言 上周写<我的编程开始(C)>这篇文章的时候,说过有时间的话会写些算法编程的题目,可能是这两天周末过的太舒适了,忘记写了.下班了,还没回去,闲来无事就写下吧. 因为写C++的编程题和其 ...

随机推荐

  1. hdu5652:India and China Origins(并查集)

    倒序操作用并查集判断是否连通,新技能get√(其实以前就会了 这题细节很多...搞得整个程序都是调试输出,几度看不下去想要重写 并查集到现在大概掌握了两个基本用途:判断是否连通 / 路径压缩(上一篇b ...

  2. P2075 [NOIP2012T5]借教室 区间更新+二分查找

    P2075 [NOIP2012T5]借教室 时间: 1000ms / 空间: 131072KiB / Java类名: Main 背景 noip2012-tg 描述 在大学期间,经常需要租借教室.大到院 ...

  3. JS 中类型鉴别

    JS中的基本类型有:数字(Number(NaN,Infinity)),字符串(String),Undefined,Null,Boolean 引用类型有:数组(Array),对象(Object),函数( ...

  4. mybatis 热部署xml文件(spring boot和springmvc两种方式)

    参考:http://thinkgem.iteye.com/blog/2304557 步骤:1.创建两个java类 (1)MapperRefresh.java   :用于刷新mapper (2)SqlS ...

  5. maven中net.sf.json报错的解决方法(转载)

    原文:http://www.cnblogs.com/winner-0715/p/5928514.html 今天在用maven添加net.sf.json的jar包的时候,代码如下: <depend ...

  6. 话说Svn与Git的区别

    这篇主要是谈谈两者的区别,至于谁优谁劣看官自己思考吧! 把第一条理解到位思想到位了做起来才会有的放矢,其他几条都是用的时候才能体会到 1) 最核心的区别Git是分布式的,而Svn不是分布的.能理解这点 ...

  7. maven中jar包的maven地址查询

    在网站 https://mvnrepository.com/ 中查找.

  8. mysql连接时权限问题 用户问题

    启动工程时会连接mysql数据库,此时报错: ERROR 1044 (42000): Access denied for user 'pay'@'localhost' to database 'pay ...

  9. [洛谷P2704] [NOI2001]炮兵阵地

    洛谷题目链接:[NOI2001]炮兵阵地 题目描述 司令部的将军们打算在NM的网格地图上部署他们的炮兵部队.一个NM的地图由N行M列组成,地图的每一格可能是山地(用"H" 表示), ...

  10. Sass 颜色函数

    /* * Sass 颜色函数 * RGB 颜色函数 * 1. rgb($red,$green,$blue):根据红.绿.蓝三个值创建一个颜色: * rgb(200,40,88) //根据r:200,g ...