最近比较忙,又有一段时间没写题目了,终于在前几天把赛码网上,JD的2016秋招和2017实习生招聘剩下的4星难度题目做了,至此所有4星难度题目都解决了,5星难度题目还剩下一个应该是计算几何学的题目,因为这块我不熟悉,后面找时间再处理。
鉴于赛码网的难度分类不是特别准确,接下里我把1-3星难度的题目过一次,如果有价值的就和大家一起分享。
这次的解析包含了4个题目,相对比较简单,但需要考虑全面,否则容易掉坑。


买糖果

<题目来源: 京东2016实习生招聘 原题链接-可在线提交(赛码网)>

问题描述

某糖果公司专门生产儿童糖果,它最受儿童欢迎的糖果有A1、A2两个序列,均采用盒式包装。包装好的A1类糖果体积为一个存储单位,而包装好的A2类糖果体积正好是A1类的两倍。

这两类糖果之所以广受儿童欢迎,是因为糖果中含有公司独家研发的魔幻因子。A1或A2序列中的糖果,看起来包装可能是一样的,但因为其中的魔幻因子含量不同被细分为不同的产品。

临近传统节日,公司的糖果供不应求。作为一个精明的糖果分销商,小东希望能够借此大赚一笔,于是带着现金开着货车来公司提货。货车的容量是确定的,小东希望采购的糖果能够尽可能装满货车,且糖果的魔幻因子总含量最高。只要不超出货车容量,糖果总可以装入货车中。

小东希望你能帮她解决这一问题。

clipboard.png

这个题目的描述似乎不是太准确,其实题目就是说有n个糖果,每个糖果有个体积(1或者2),此外,有个魔幻因子。最后我们需要解决是在体积不超过v的情况下,选择若干糖果并且尽可能的获得最大的魔幻因子总量。

这看起来是个01背包问题*[1],观察数据规模后发现,n <= 10^5,v <= 10^9这个用于状态转移的枚举量实在太大,而且单就一个v的上限就足以撑爆空间。看来问题似乎不是直接使用01背包的动态规划可以解决的,再观察题目条件,一个比较特别的地方是糖果的体积只有1和2的两种情况,我们从这个比较特殊的条件来解决问题。

a.考虑一种最简单的情况:只有体积为1的糖果。
显然我们只需要对所有的糖果按照魔幻因子从大到小排序,从魔幻因子最大的糖果开始选择,由于体积都是1,那么给定的v一定可以达到。这些选择出来的糖果的魔幻因子的和就是我们的求解目标。

b.再考虑另外一种罪简单的情况,只有体积为2的糖果。
同样我们需要按魔幻因子的大小进行排序,然后从魔幻因子最大的糖果开始选择,但是不同于a,由于是每个糖果的体积是2,那么选出来糖果的体积和不一定可以刚好为v,如果刚好为v则问题已经解决。如果超过v,显然是总体积比v大了1,那么最后选择的这一种糖果就不要了。这时我们选择的糖果的总体积为v-1,尽管浪费了1,但是我们没有任何办法再放入糖果了。

c.考虑题目既有体积1和2的情况。
基于上述两种基本情况的分析,我们仍然需要安排魔幻因子大小进行排序,尽可能选择魔幻因子高的优先选择,再考虑临界情况的处理。需要注意到,对于体积为2的糖果,我们不应该直接按照其魔幻因子的总量进行排序,而是按照单位体积的魔幻因子含量进行排序。这是因为,尽管可能体积为2的糖果魔幻因子可能很高,但是他占用2个体积,如果两个体积为1的糖果魔幻因子没有其高,但是两个加起来就超过了,并且最终占用的体积都是2.

排序之后我们按照从大的开始选择,如果可以刚好取到v,那么问题就得到解决。如果得不到,那么显然当前取的是体积2的糖果,并且是超过1。这个时候,明显我们可以有多种选择,我们既可以在已经选择的糖果中去掉一个体积为1的且魔幻因子最小的糖果,也可以是在还没有选择的糖果中选择一个体积为1的且魔幻因子最大的一个糖果。至于具体选择哪种就看哪种方案可以获得更高的魔幻因子总和了。

那么问题似乎已经解决了,然后我们并没有考虑到选择的和没选择的中都可能不存在体积为1的糖果。
...已选择部分...| 体积为2的糖果| ...未选择部分...

 ^已获得的魔幻因子pm ^体积限制v

设已选择的最小的体积为1的糖果的魔幻因子为l
设未选择的最小的体积为1的糖果的魔幻因子为r
当前体积2的糖果魔幻因子为p,体积不超过v获得最大的魔幻因子总和为pm
i.l和r都不存在

  1. = v
    ii.l存在,r不存在
  2. = max(pm, pm - l + p)

iii.l不存在,r存在

  1. = pm + r
    iv.l和r都存在
  2. = max(pm + r, pm - l + p)

最后一个需要解决的问题是输出选择糖果的序列问题,这并不难,我们排序后依次记录选择了的糖果即可。pm - l + p的情况,删除记录中的l即可。但是,由于存在多种方案要得失编号尽可能的小,那么对与ii, iv中存在相等的情况时的处理原则时,选择编号较小的即可。

排序时间复杂度为O(nlogn),由于每个糖果只需要处理一次,计算部分时间复杂度为O(n),非常理想。

import sys

const_n = 0
const_v = 1
const_p = 2


def main():
    while True:
        line = map(int, sys.stdin.readline().strip().split())
        n, vm = line[0], line[1]

        candies = []
        for i in range(n):
            line = map(int, sys.stdin.readline().strip().split())
            if len(line) < 2:
                break
            candies.append((i + 1, line[0], line[1]))

        candies = sorted(candies, key=lambda s: float(s[const_p]) / float(s[const_v]), reverse=True)
        # print candies

        seq = set([])
        v = p = 0
        l = r = -1
        for i, candy in enumerate(candies):
            if v + candy[const_v] >= vm:
                if v + candy[const_v] == vm:
                    seq.add(candy[const_n])
                    p += candy[const_p]
                else:
                    for j in range(i + 1, n):
                        if candies[j][const_v] == 1:
                            r = j
                            break

                    if l == -1 and r == -1:
                        pass
                    elif l == -1 and r != -1:
                        p += candies[r][const_p]
                        seq.append(candies[r][const_n])
                    elif l != -1 and r == -1:
                        if candy[const_p] - candies[l][const_p] > 0:
                            p = p - candies[l][const_p] + candy[const_p]
                            seq.remove(candies[l][const_n])
                            seq.add(candy[const_n])
                    else:
                        if candy[const_p] - candies[l][const_p] > candies[r][const_p]:
                            p = p - candies[l][const_p] + candy[const_p]
                            seq.remove(candies[l][const_n])
                            seq.add(candy[const_n])
                        else:
                            p += candies[r][const_p]
                            seq.add(candies[r][const_n])
                break

            seq.add(candy[const_n])
            v += candy[const_v]
            p += candy[const_p]

            if candy[const_v] == 1:
                l = i

        print p
        if p > 0:
            seq = sorted(seq)
            for s in seq:
                print s,
        else:
            print 'No'
        print

if __name__ == '__main__':
    main()

终结者C

<题目来源: 京东2017实习生招聘 原题链接-可在线提交(赛码网)>

问题描述

收到情报,有批新造的机器人要运输到前线。小C将去破坏机器人的运输。小C将激光炮放置在公路的一旁,等运输车经过的时候发射(假设激光炮一定可以射穿车辆)。由于能源有限,激光炮只能发射两次。可以认为激光炮放在坐标轴的原点处,并向y轴正方向发射。每辆运输车可以看作是一个矩形,起始的x轴坐标为Xi ,所有的车均位于第一象限,长度为Li,速度为1,朝x轴负方向运动。即经过t时间后,该车车头的x坐标为Xi-t,车尾坐标为Xi-t+Li 。只要打中车的任何一个部分就算击中。
请你算算,他在哪两个时刻发射,才能摧毁最多的运输车。

clipboard.png

clipboard.png

先简化下题目的意思,由于每个车辆的前进速度是统一的,那么实际上我们就不用考虑车子移动的情况了,只需要在x轴上选择两个点发射激光炮即可。此外,这个题目的数据规模是X、L<= 10^9 而不是109。

需要注意,第2次发射的激光炮不会击毁被第一次发射的激光炮已经击毁的车子。也就是1辆车不能被击毁两次。我们就不能单纯在按某个点上的车辆最多和次多来选择x轴上发射激光炮的位置了。考虑到只能发射两枚激光炮,那么我们只需要枚举这2个位置就可以了

但是观察X和L 10^9的限时,直接枚举x轴上的两个点显然不可以。

我们考虑两个车有重叠的情况,要判断有没有办法一炮机会这两个车子,实际上我们只需要判断两个位置即可,即某个车头的点是在另外一个车的范围内即可。这是一种对于极限情况的考虑,如果该情况成立,就还会有若干其他的点可能满足,但这部分点就不需要再判断。换句话说,两个车如果有重叠,那么其中有个车的车头一定在另外一个车的车身范围内

扩展到一般情况,根据上面的讨论,对于所有的车,我们激光炮的发射位置只需要选择每个车车头的位置即可。枚举之后再判断哪些车子被击毁,做上标记。最后记录一个最大的击毁数目即可。由于车辆总数n仅仅200,先两重循环枚举2个车头位置,再用一层循环检查击毁数目,算法的时间复杂度为O(n^3)是可以接受的。

import sys


def calc(f, s, c, n):
    destroy = [False for i in range(n)]
    res = 0

    for i, elem in enumerate(c):
        if elem[0] <= f <= elem[1] or elem[0] <= s <= elem[1]:
            if not destroy[i]:
                res += 1
            destroy[i] = True

    return res


def main():
    n = map(int, sys.stdin.readline().strip().split())[0]
    cars = []
    segments = []

    for i in range(0, n):
        line = map(int, sys.stdin.readline().strip().split())
        cars.append((line[0], line[0] + line[1]))
        segments.append(line[0])

    r = 0
    for i in range(len(segments)):
        for j in range(i):
            r = max(r, calc(segments[i], segments[j], cars, n))

    print r


if __name__ == '__main__':
    main()

幸运数

<题目来源: 京东2017秋招 原题链接-可在线提交(赛码网)>

问题描述

小明同学学习了不同的进制之后,拿起了一些数字做起了游戏。小明同学知道,在日常生活中我们最常用的是十进制数,而在计算机中,二进制数也很常用。现在对于一个数字x,小明同学定义出了两个函数f(x)和g(x)。
f(x)表示把x这个数用十进制写出后各个数位上的数字之和。如f(123)=1+2+3=6。
g(x)表示把x这个数用二进制写出后各个数位上的数字之和。如123的二进制表示为1111011,那么g(123)=1+1+1+1+0+1+1=6。
小明同学发现对于一些正整数x满足f(x)=g(x),他把这种数字称为幸运数,现在他想知道,小于等于n的幸运数有多少个。

clipboard.png

接下来的这两个题目相对比较简单,数据范围也不大,这个题目可以直接按照题目意思模拟即可。
注意以下两点问题:
1.这类题目都要采用离线的计算方式,一次性先把数据规模内的数据全部计算到出来并保存,然后根据输出直接输出结果即可。
2.相对来说这个题目的数据规模较弱,注意到在当次计算时,如果个位没有发生进位的话,结果就是上次计算的结果+1,如果发生了进位再全部计算一次即可。这样可以节约非常大的一个计算量。

提交后我大致看了下运行时间,我使用的python耗时369ms,而其他python和java耗时接近或者超过1000ms,其中一个C++的运行时间为623ms。有时候一个很小的优化带来的性能提升是很显著的。

import sys

const_max_n = 100000 + 1


def _next(bits, last_sum, base):
    next_add = 0
    bits[0] += 1
    last_sum += 1

    if bits[0] >= base:
        last_sum = 0
        for i in range(len(bits)):
            bits[i] += next_add
            if bits[i] >= base:
                bits[i] = 0
                next_add = 1
            else:
                next_add = 0
            last_sum += bits[i]

        if next_add == 1:
            bits.append(1)
            last_sum += 1

    return last_sum


def main():
    r = [0 for i in range(0, const_max_n)]
    _dec = [0]
    _bin = [0]
    last_bin_sum = last_dec_sum = 0

    for i in range(1, const_max_n):
        last_bin_sum = _next(_dec, last_bin_sum, 2)
        last_dec_sum = _next(_bin, last_dec_sum, 10)

        r[i] = r[i - 1]
        if last_bin_sum == last_dec_sum:
            r[i] += 1

    t_case = map(int, sys.stdin.readline().strip().split())[0]
    for i in range(0, t_case):
        n = map(int, sys.stdin.readline().strip().split())[0]
        print r[n]


if __name__ == '__main__':
    main()

三子棋

<题目来源: 京东2016实习生招聘 原题链接-可在线提交(赛码网)>

问题描述

三子棋是一种大家熟知的游戏,几乎所有人都会玩。游戏规则相当简单,两人依次在一个3X3棋盘格上下棋,一个人画叉,另一个人画圈。任何一个人画的三个记号如果形成构成一条水平、垂直或对角的直线则获胜,游戏结束。画叉的人先开始游戏,如果所有的棋盘格都画满了但两人都不能获胜,则游戏平局结束。

游戏在一个3X3的棋盘上进行,每个棋盘格单元处于空白、画叉或画圈状态中的一种,你的任务是确定下一轮由谁下棋:
1:轮到先手下棋;
2:轮到后手下棋;

或者是判定游戏的状态:
x:给定的棋局不是合法的棋局;
1 won:先手获胜;
2 won:后手获胜;
Draw:平局;

小东对棋类游戏很有研究,这一次三子棋比赛中,她被邀请作为评判,为了提携后进,她请你帮忙判定。

clipboard.png
本题看似简单,其实要注意的地方还比较多,包括如何比较优雅的完成代码的编写(这我代码也写不太好)。

注意到本题的条件具有先后顺序。应该是下列顺序:
1.给定的棋局是否合法;
2.是否有获胜方;
3.平局
4.该谁下棋

这类题目建议使用一个方向向量,方向向量可以指示要判断的方向。比如,要判断一个从上到下的对角线情况,我们可以设置一个(1,1)的方向向量,当从(0,0)开始后:

  1. = (0, 0)

sx = 0, sy = 0
for i <- 0 to 2:
sx += d[0]
sy += d[1]
即可,对于本题,设置一个判断8个方向的起始位置后,再对应设置8个方向向量即可。这样对于我们编写代码十分有利。可以最大限度的减少代码量,便于阅读。

import sys

start = [(2, 0), (1, 0), (0, 0), (0, 0), (0, 0), (0, 1), (0, 2), (2, 0)]
vector = [(0, 1), (0, 1), (0, 1), (1, 1), (1, 0), (1, 0), (1, 0), (-1, 1)]

def main():

while True:
    x = o = 0
    board = []
    for i in range(3):
        line = map(str, sys.stdin.readline().strip().split())
        if len(line[0]) < 3:
            return
        for l in line[0]:
            if l == 'X':
                x += 1
            elif l == '0':
                o += 1
        board.append(list(line[0]))

    legal = True
    w1 = w2 = False

    if 0 <= x - o <= 1:
        for i in range(8):
            d = {'0': 0, 'X': 0, '.': 0}
            dx, dy = start[i][0], start[i][1]
            for j in range(3):
                t = d.get(board[dx][dy])
                d[board[dx][dy]] = t + 1
                dx += vector[i][0]
                dy += vector[i][1]

            if d.get('X') == 3:
                w1 = True
            if d.get('0') == 3:
                w2 = True

        if w1 and w2 or x > o and w2 or x == o and w1:
            legal = False

    else:
        legal = False

    if legal:
        if w1:
            print '1 won'
        elif w2:
            print '2 won'
        else:
            if x + o == 9:
                print 'draw'
            elif x == o:
                print '1'
            else:
                print '2'
    else:
        print 'x'

if name == '__main__':

main()


*[1] 关于01背包问题及其扩展可以参考下列文章:
http://blog.csdn.net/mu399/ar...
http://blog.csdn.net/insistgo...


lite
43 声望6 粉丝

null