【每日一练】【暴力求解】堆棋子

问题描述:

小易将n个棋子摆放在一张无限大的棋盘上。第i个棋子放在第x[i]行y[i]列。同一个格子允许放置多个棋子。每一次操作小易可以把一个棋子拿起并将其移动到原格子的上、下、左、右的任意一个格子中。小易想知道要让棋盘上出现有一个格子中至少有i(1 ≤ i ≤ n)个棋子所需要的最少操作次数.

输入描述:

输入包括三行,第一行一个整数n(1 ≤ n ≤ 50),表示棋子的个数
第二行为n个棋子的横坐标x[i](1 ≤ x[i] ≤ 10^9)
第三行为n个棋子的纵坐标y[i](1 ≤ y[i] ≤ 10^9)

输出描述:

输出n个整数,第i个表示棋盘上有一个格子至少有i个棋子所需要的操作数,以空格分割。行末无空格

如样例所示:

对于1个棋子: 不需要操作
对于2个棋子: 将前两个棋子放在(1, 1)中
对于3个棋子: 将前三个棋子放在(2, 1)中
对于4个棋子: 将所有棋子都放在(3, 1)中

示例1

输入

4
1 2 4 9
1 1 1 1

输出

0 1 3 10

思路:
本题一开始理解错意思了,题目不是单纯的想要给前i个棋子找到一个汇聚点,使前i个棋子到这个点的曼哈顿距离最小。而是每次都是对于所有的棋子,找到一个点,使移动到这个点上的棋子数分别为1,2,……n时需要移动的次数。
例如对于输入示例:

5
8 1 9 9 7
7 7 7 7 10

输出:

0 0 1 6 13

输出解释:

对于1个棋子,不需要移动
对于2个棋子,已经存在坐标(9,7)上有两个棋子,也不需要移动
对于3个棋子,将(8,7)移动到(9,7)上,步数为1次
对于4个棋子,(8,7)->(9,7),(7,10)->(9,7),步数为1+5=6
对于5个棋子,(1,7)->(8,7),(9,7)->(8,7),(9,7)->(8,7),(7,10)->(8,7),步数为7+1+1+4=13

很显然这样一个点的坐标必然不会超过所有棋子构成的最大矩形范围,所以我们可以遍历这个矩阵内所有点,分别计算其与棋子坐标的距离,取前k个距离最近的点;选出所有可能汇聚点的前k个距离最近点之和中的最小值,即为所求。

当然上面的思路是纯粹暴力,我们可以对它进行一定优化,分析一下目的坐标可能的位置。牛客网友根据@蟹粉馅大糖包 的证明,最优聚合点的x坐标一定是已知棋子的x坐标之一,y坐标也是已知棋子的x坐标之一。结论的证明采用的是反证法,实际上x轴和y轴的移动互不影响,因此只需要证明一维。

假设棋子汇聚点X0,并且这个点不等于已知任一棋子坐标,左边有a个棋子,汇聚到X0总步数为m,右边有b个棋子,汇聚到X0总步数为n。若a>b,那么在该汇聚点左边,必然存在一个离X0最近的棋子坐标Xp,使得所有棋子汇聚到该点步数为

(m(a1)(X0Xp)+n+b(X0Xp))=(m+n+(ba+1)(X0Xp))<=(m+n) ( m − ( a − 1 ) ∗ ( X 0 − X p ) + n + b ∗ ( X 0 − X p ) ) = ( m + n + ( b − a + 1 ) ∗ ( X 0 − X p ) ) <= ( m + n )
当且仅当a=b+1时等号成立。a < b同理。

这个结论看似不符合题目给出的示例,实际上对于4个棋子,可以将所有棋子放在(4,1)位置,所需操作也是10次。

def calcu_distance(x1, x2, y1, y2):
    return abs(x1 - x2) + abs(y1 - y2)


def play_chess():
    n = int(input())
    x = list(map(int, input().split()))
    y = list(map(int, input().split()))
    ans = [float('inf')] * n
    for i in range(n):
        for j in range(n):
            dis_arr = []
            for k in range(n):
                # 以(x[i],y[j])作为汇聚点,计算每一个棋子(x[k],y[k])到它的距离
                dis = calcu_distance(x[k], x[i], y[k], y[j])
                dis_arr.append(dis)
            # 对距离进行排序
            dis_arr.sort()
            for m in range(n):
                # 其他汇聚点得到的距离之和比较,选最小的
                ans[m] = min(ans[m], sum(dis_arr[:m + 1]))
    print(' '.join([str(a) for a in ans]))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值