【动态规划】【前缀和】【推荐】2463. 最小移动总距离

简介: 【动态规划】【前缀和】【推荐】2463. 最小移动总距离

作者推荐

【广度优先搜索】【网格】【割点】【 推荐】1263. 推箱子

本文涉及知识点

动态规划汇总

C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频

2463. 最小移动总距离

X 轴上有一些机器人和工厂。给你一个整数数组 robot ,其中 robot[i] 是第 i 个机器人的位置。再给你一个二维整数数组 factory ,其中 factory[j] = [positionj, limitj] ,表示第 j 个工厂的位置在 positionj ,且第 j 个工厂最多可以修理 limitj 个机器人。

每个机器人所在的位置 互不相同 。每个工厂所在的位置也 互不相同 。注意一个机器人可能一开始跟一个工厂在 相同的位置 。

所有机器人一开始都是坏的,他们会沿着设定的方向一直移动。设定的方向要么是 X 轴的正方向,要么是 X 轴的负方向。当一个机器人经过一个没达到上限的工厂时,这个工厂会维修这个机器人,且机器人停止移动。

任何时刻,你都可以设置 部分 机器人的移动方向。你的目标是最小化所有机器人总的移动距离。

请你返回所有机器人移动的最小总距离。测试数据保证所有机器人都可以被维修。

注意:

所有机器人移动速度相同。

如果两个机器人移动方向相同,它们永远不会碰撞。

如果两个机器人迎面相遇,它们也不会碰撞,它们彼此之间会擦肩而过。

如果一个机器人经过了一个已经达到上限的工厂,机器人会当作工厂不存在,继续移动。

机器人从位置 x 到位置 y 的移动距离为 |y - x| 。

示例 1:

输入:robot = [0,4,6], factory = [[2,2],[6,2]]

输出:4

解释:如上图所示:

  • 第一个机器人从位置 0 沿着正方向移动,在第一个工厂处维修。
  • 第二个机器人从位置 4 沿着负方向移动,在第一个工厂处维修。
  • 第三个机器人在位置 6 被第二个工厂维修,它不需要移动。
    第一个工厂的维修上限是 2 ,它维修了 2 个机器人。
    第二个工厂的维修上限是 2 ,它维修了 1 个机器人。
    总移动距离是 |2 - 0| + |2 - 4| + |6 - 6| = 4 。没有办法得到比 4 更少的总移动距离。
    示例 2:
    输入:robot = [1,-1], factory = [[-2,1],[2,1]]
    输出:2
    解释:如上图所示:
  • 第一个机器人从位置 1 沿着正方向移动,在第二个工厂处维修。
  • 第二个机器人在位置 -1 沿着负方向移动,在第一个工厂处维修。
    第一个工厂的维修上限是 1 ,它维修了 1 个机器人。
    第二个工厂的维修上限是 1 ,它维修了 1 个机器人。
    总移动距离是 |2 - 1| + |(-2) - (-1)| = 2 。没有办法得到比 2 更少的总移动距离。
    提示:
    1 <= robot.length, factory.length <= 100
    factory[j].length == 2
    -109 <= robot[i], positionj <= 109
    0 <= limitj <= robot.length
    测试数据保证所有机器人都可以被维修。

动态规划

原理

性质一:r1和r2是两个机器人位置,且r1 < r2。令f1和r2是两个工厂位置,且f1 < f2。 选择一:f1修r1,f2修r2。选择二:f1修r2,f2修f1。方式一不劣于方式二

下面分情况证明:

一,r2 <= f1。两个机器人都在左边,两种选择结果一样。可以这样理解,两个机器人都进f1,然后其中一个去f2。

二,r1 >= f2。两个机器人都在右边。两种选择结果一样。可以这样理解:两个机器人都近f2,然后其中一个去f1。

三,f1 < r1 < r2 < f2。绿线是选择一,红线是选择二。

四,r1 < f1 f1 < r2 < f2 。

五,r1 < f1 r2 > f2。

六,f1<r1<f2 r2<r2。和情况四对称。

动态规划的状态表示

dp[i][j] 前i个工厂修理前j个机器人的最小移动距离。

动态规划的转移方程

最左边的i个工厂,修理了j个机器人,最后一个工厂修理了k个机器人。根据性质一,则这k个机器人一定是j个机器人中最右边的。

dp[i][j] = m i n k : 0 m i n ( l i m i t [ i ] , j ) \Large min_{k:0}^{min(limit[i],j)}mink:0min(limit[i],j) (pre[i-1][j-k])+移动k个机器人的总距离

左移的机器人和右移的机器人,分别利用前缀和计算总距离。先对机器人和工厂的位置排序,坐标小于等于工厂坐标机器人右移:

∑ 移动的机器人 ( 工厂到原点距离 − 机器人到原点的距离 ) → \sum _{移动的机器人} (工厂到原点距离-机器人到原点的距离) \rightarrow移动的机器人(工厂到原点距离机器人到原点的距离)工厂到原点的距离 ⋆ 移动的机器人数 − ∑ 移动的机器人 ( 机器人到原点的距离 ) 工厂到原点的距离\star 移动的机器人数 - \sum _{移动的机器人}(机器人到原点的距离)工厂到原点的距离移动的机器人数移动的机器人(机器人到原点的距离)

动态规划的初始值

dp[0][0]=0 其它3e11

动态规划的填表顺序

从左到右计算后置状态。

返回值

dp.back().back()

注意

前缀和记录的是相对位置:坐标是负数也可以。

代码

核心代码

template<class ELE,class ELE2>
void MinSelf(ELE* seft, const ELE2& other)
{
  *seft = min(*seft,(ELE) other);
}
template<class ELE>
void MaxSelf(ELE* seft, const ELE& other)
{
  *seft = max(*seft, other);
}
template<class T = long long >
class CPreSum
{
public:
  CPreSum(const vector<int>& nums)
  {
    m_data.push_back(0);
    for (int i = 0; i < nums.size(); i++)
    {
      m_data.push_back(m_data[i] + nums[i]);
    }
  }
  template<class _PR>
  CPreSum(int iSize, _PR pr)
  {
    m_data.push_back(0);
    for (int i = 0; i < iSize; i++)
    {
      m_data.push_back(m_data[i] + pr(i));
    }
  }
  T Sum(int left, int rightExclu)const
  {
    return m_data[rightExclu] - m_data[left];
  }
protected:
  vector<T> m_data;
};
class Solution {
public:
  long long minimumTotalDistance(vector<int>& robot, vector<vector<int>>& factory) {
    sort(robot.begin(), robot.end());
    sort(factory.begin(), factory.end(), [&factory](auto& v1,auto& v2) {return v1[0] <v2[0]; });
  
    int n1 = factory.size(), n2 = robot.size();
    vector<int> vLeftCount;
    for (int i = 0, j = 0; i < n1; i++)
    {
      while ((j < n2) && (robot[j] <= factory[i][0]))
      {
        j++;
      }
      vLeftCount.emplace_back(j);
    }
    CPreSum preSum(robot);
    vector<vector<long long>> dp(n1 + 1, vector<long long>(n2 + 1,3e11));
    dp[0][0] = 0;
    for (int i = 1; i <= n1; i++)
    {
      dp[i][0] = 0;
      for (int j = 1; j <= n2; j++)
      {
        for (int k = 0; k <= min(j, factory[i - 1][1]); k++)
        {
          const long long iLefttMove = min(k,max(0, j - vLeftCount[i - 1]));
          const long long llRightMove = k - iLefttMove;
          long long llMove = preSum.Sum(j - iLefttMove, j) - iLefttMove * factory[i - 1][0];//左移
          llMove += factory[i - 1][0] * llRightMove - preSum.Sum(j-k,j-k+llRightMove);
          MinSelf(&dp[i][j], dp[i - 1][j - k] + llMove);
        }
      }
    }
    return dp.back().back();
  } 
};

核心代码

template<class T>
void Assert(const T& t1, const T& t2)
{
  assert(t1 == t2);
}
template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
  if (v1.size() != v2.size())
  {
    assert(false);
    return;
  }
  for (int i = 0; i < v1.size(); i++)
  {
    Assert(v1[i], v2[i]);
  }
}
int main()
{ 
  vector<int> robot;
  vector<vector<int>> factory;
  {
    Solution sln;
    robot = { 0,4,6 }, factory = { {2,2},{6,2} };
    auto res = sln.minimumTotalDistance(robot,factory);
    Assert(res,4LL);
  }
  {
    Solution sln;
    robot = { 1,-1 }, factory = { {-2,1},{2,1} };
    auto res = sln.minimumTotalDistance(robot, factory);
    Assert(res, 2LL);
  }
  {
    Solution sln;
    robot = { 0,4,6 }, factory = { {-2,2},{-6,2} };
    auto res = sln.minimumTotalDistance(robot, factory);
    Assert(res, 20LL);
  }
    
}

2023年2月

class Solution {

public:

long long minimumTotalDistance(vector& robot, vector<vector>& factory) {

m_c = robot.size();

std::sort(robot.begin(), robot.end());

std::sort(factory.begin(), factory.end(), [](const vector& v0, const vector& v1)

{

return v0[0] < v1[0];

});

vector vPre(m_c + 1, m_llNotMay);

vPre[0] = 0;

for (const auto& v : factory)

{

vector dp = vPre;

for (int i = 0; i < m_c; i++)

{

if (m_llNotMay == vPre[i])

{

continue;

}

long long llMove = 0;

for (int j = 1; j <= v[1]; j++)

{

if (i + j > m_c)

{

break;

}

llMove += abs(v[0] - robot[i + j - 1]);

dp[i + j] = min(dp[i + j], vPre[i] + llMove);

}

}

vPre.swap(dp);

}

return vPre[m_c];

}

int m_c;

const long long m_llNotMay = ((long long)INT_MAX) * 1000;

};

2023年8月

class Solution {

public:

long long minimumTotalDistance(vector& robot, vector<vector>& factory) {

m_c = robot.size();

std::sort(robot.begin(), robot.end());

std::sort(factory.begin(), factory.end(), [](const vector& v1, const vector& v2)

{return v1[0] < v2[0]; });

vector vPre(m_c + 1, m_llNotMay);//vPre[i] 当前工厂及更早工厂修理i个机器人的最小移动距离

vPre[0] = 0;

for (const auto& v : factory)

{

vector dp = vPre;//本工厂不修理机器人

for (int pre = 0; pre < m_c; pre++)

{

long long llMove = 0;

for (int iDo = 1; iDo <= v[1]; iDo++)

{

const int next = pre + iDo;

if (next > m_c)

{

break;

}

llMove += abs(robot[next-1] - v[0]);

dp[next] = min(dp[next], vPre[pre] + llMove);

}

}

vPre.swap(dp);

}

return vPre.back();

}

int m_c;

const long long m_llNotMay = 1e12;

};

2023年9月

class Solution {

public:

long long minimumTotalDistance(vector& robot, vector<vector>& factory) {

sort(robot.begin(), robot.end());

sort(factory.begin(), factory.end(), [](const vector& v1, const vector& v2)

{

return v1[0] < v2[0];

});

long long llMinPos = min(*robot.begin(),(*factory.begin())[0]);

vector vPreSumDis(1);

for (const auto& n : robot)

{

vPreSumDis.emplace_back(vPreSumDis.back() + n- llMinPos);

}

vector pre(1);//pre[i] 当前站点之前的站点修改i个机器人的最小成本

int iRight = 0;//当前工厂右边的第一个机器人

for (int i = 0; i < factory.size(); i++)

{

const int iFactoryPos = factory[i][0];

for (; (iRight < robot.size()) && (robot[iRight] <= iFactoryPos); iRight++);

const int iSize = min(robot.size(), pre.size()-1 + factory[i][1]);

vector dp(iSize+1,1e12);

for (int j = 0; j<pre.size(); j++)

{

for (int k = 0; (k <= factory[i][1])&&(j+k <= robot.size()); k++)

{//[j,iRight)个机器上右移[iRight,j+k)个机器人左右移

const long long llMove = MoveRight(j, min(j + k, iRight), iFactoryPos, llMinPos, vPreSumDis)+

MoveLeft(max(j,iRight),j+k, iFactoryPos, llMinPos, vPreSumDis);

dp[j + k] = min(dp[j + k], llMove+pre[j]);

}

}

pre.swap(dp);

}

return pre.back();

}

long long MoveRight(int left, int right,const int iFactoryPos,const long long llMinPos, const vector& vPreSumDis)

{

if (right <= left )

{

return 0;

}

return (long long)(right - left) * (iFactoryPos - llMinPos) - (vPreSumDis[right] - vPreSumDis[left]);

}

long long MoveLeft(int left, int right, const int iFactoryPos, const long long llMinPos, const vector& vPreSumDis)

{

if (right <= left)

{

return 0;

}

return vPreSumDis[right] - vPreSumDis[left] - (long long)(right - left) * (iFactoryPos - llMinPos);

}

};


相关文章
|
7月前
|
人工智能 移动开发 算法
【动态规划】【C++算法】LeetCoce996正方形数组的数目
【动态规划】【C++算法】LeetCoce996正方形数组的数目
|
7月前
|
机器学习/深度学习 算法 测试技术
【动态规划】【最长子序列】2901. 最长相邻不相等子序列 II
【动态规划】【最长子序列】2901. 最长相邻不相等子序列 II
|
7月前
leetcode-435:无重叠区间
leetcode-435:无重叠区间
35 0
|
算法
【算法专题突破】双指针 - 有效三角形的个数(5)
【算法专题突破】双指针 - 有效三角形的个数(5)
35 0
|
7月前
|
算法 测试技术 C#
【动态规划】【前缀和】【和式变换】100216. K 个不相交子数组的最大能量值
【动态规划】【前缀和】【和式变换】100216. K 个不相交子数组的最大能量值
|
7月前
DAY-4 | 力扣 - 求自身以外数组的乘积:区间划分,左右累乘,巧求乘积
该文档是关于LeetCode上的一道题目“Product of Array Except Self”的题解。提供了两种解题方法,一是暴力破解,即计算所有数的乘积后再逐个除以当前元素;二是左右累乘法,通过两次遍历数组分别计算左侧和右侧元素的乘积,避免了除法操作。其中,左右累乘法更优,代码实现中展示了这种方法。
51 1
|
7月前
|
算法 测试技术 C++
【动态规划 区间dp 位运算】3117. 划分数组得到最小的值之和
【动态规划 区间dp 位运算】3117. 划分数组得到最小的值之和
【动态规划 区间dp 位运算】3117. 划分数组得到最小的值之和
|
7月前
|
算法 C++
【动态规划】【矩阵】C++算法329矩阵中的最长递增路径
【动态规划】【矩阵】C++算法329矩阵中的最长递增路径
|
7月前
|
算法 测试技术 C++
【组合数学】【动态规划】【前缀和】1735生成乘积数组的方案数
【组合数学】【动态规划】【前缀和】1735生成乘积数组的方案数
|
7月前
|
算法
【面试算法——动态规划 20】最长公共子序列&& 不相交的线
【面试算法——动态规划 20】最长公共子序列&& 不相交的线

热门文章

最新文章