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. 洛谷 P2898 [USACO08JAN]haybale猜测Haybale Guessing 解题报告

    [USACO08JAN]haybale猜测Haybale Guessing 题目描述 给一段长度为\(n\),每个位置上的数都不同的序列\(a[1\dots n]\)和\(q\)和问答,每个问答是\( ...

  2. 洛谷 P3380 bzoj3196 Tyvj1730 【模板】二逼平衡树(树套树)

    [模板]二逼平衡树(树套树) 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作: 查询k在区间内的排名 查询区间内排名为k的值 修改某一位值上的数值 查询k在 ...

  3. 洛谷 P1363 幻想迷宫 解题报告

    P1363 幻想迷宫 题目描述 背景 Background (喵星人LHX和WD同心协力击退了汪星人的入侵,不幸的是,汪星人撤退之前给它们制造了一片幻象迷宫.) WD:呜呜,肿么办啊-- LHX:mo ...

  4. Object.defineProperty基本用法

    1. 基本形式 Object.defineProperty(obj,prop,descriptor) 参数说明: obj: 必需,目标对象prop: 必需,需定义或修改属性的名字descriptor: ...

  5. 直通BAT面试算法精讲课1

    1.有一棵二叉树,请设计一个算法,按照层次打印这棵二叉树. 给定二叉树的根结点root,请返回打印结果,结果按照每一层一个数组进行储存,所有数组的顺序按照层数从上往下,且每一层的数组内元素按照从左往右 ...

  6. MyEclipse安装FreeMarker插件

      MyEclipce8.6中安装FreeMarker插件,这绝对是最简单的方法.   步骤如下:   (一)打开http://sourceforge.net/projects/freemarker- ...

  7. weakself的另一种写法

    在不久前看AFNetworking的源码时候发现了这么一句: 1 2 3 4 5 6 7 8 9 10 // 不知道这行代码的使用场景的同学你该去自习看看ARC的注意事项和Block的使用了 // A ...

  8. 2115: [Wc2011] Xor (线性基+dfs)

    2115: [Wc2011] Xor Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 5714  Solved: 2420 题目链接:https://w ...

  9. VC使用sqlite

    SQLite可以到官方站点(http://www.sqlite.org/download.html)下载:Linux,Mac OS X, Windows下的已编译文件以及源代码.帮助文档. SQLit ...

  10. [vim]大小写转换

    http://babybandf.blog.163.com/blog/static/619935320110121134826/ ~ 将光标下的字母改变大小写 3~ 将光标位置开始的3个字母改变其大小 ...