【状态机dp】【 排序 】 2809使数组和小于等于 x 的最少时间

本文涉及知识点

【状态机dp】 排序

LeetCode 2809. 使数组和小于等于 x 的最少时间

给你两个长度相等下标从 0 开始的整数数组 nums1 和 nums2 。每一秒,对于所有下标 0 <= i < nums1.length ,nums1[i] 的值都增加 nums2[i] 。操作 完成后 ,你可以进行如下操作:
选择任一满足 0 <= i < nums1.length 的下标 i ,并使 nums1[i] = 0 。
同时给你一个整数 x 。
请你返回使 nums1 中所有元素之和 小于等于 x 所需要的 最少 时间,如果无法实现,那么返回 -1 。
示例 1:
输入:nums1 = [1,2,3], nums2 = [1,2,3], x = 4
输出:3
解释:
第 1 秒,我们对 i = 0 进行操作,得到 nums1 = [0,2+2,3+3] = [0,4,6] 。
第 2 秒,我们对 i = 1 进行操作,得到 nums1 = [0+1,0,6+3] = [1,0,9] 。
第 3 秒,我们对 i = 2 进行操作,得到 nums1 = [1+1,0+2,0] = [2,2,0] 。
现在 nums1 的和为 4 。不存在更少次数的操作,所以我们返回 3 。
示例 2:

输入:nums1 = [1,2,3], nums2 = [3,3,3], x = 4
输出:-1
解释:不管如何操作,nums1 的和总是会超过 x 。

提示:

1 <= nums1.length <= 103
1 <= nums1[i] <= 103
0 <= nums2[i] <= 103
nums1.length == nums2.length
0 <= x <= 106

状态机dp

sum1 = ∑ \sum nums1 sum2 = ∑ \sum nums2 n = nums1.length
sum1的最终值有两部分组成:
一,每秒增加sum2。
二,清零。减少当前nums1[i]。
性质一: 对任意i清零两次,和只清第二次减少的值一样。
性质二:没必要对任意i清零两次,删除第一次清零i。最终值减少sum2。
性质三:由于不存在重复的i,故如果n秒搞不定,则永远搞不定。
性质四:如果某个最优解清零了S = {i1,i2,i3 … \dots },那么按nums2[i]的升序清零,一定是最优解,i ∈ \in S。

解法

indexs是nums2的下标,nums[indexs[i]] 升序排序。
dp[i][j] 表示处理完indexs的前i个数,操作了j秒清0的最大值

错误想法

看题解前,最终选取了m个,已经选择了j个。

代码

核心代码

 class Solution {
  public:
	  int minimumTime(vector<int>& nums1, vector<int>& nums2, int x) {
		  const int n = nums1.size();
		  const int sum1 = std::accumulate(nums1.begin(), nums1.end(), 0);
		  const int sum2 = std::accumulate(nums2.begin(), nums2.end(), 0);
		  if (sum1 <= x) { return 0; }
		  vector<int> indexs(n);
		  iota(indexs.begin(), indexs.end(), 0);
		  sort(indexs.begin(), indexs.end(), [&](const int& i1, const int& i2) {return nums2[i1] < nums2[i2]; });
		  vector<vector<int>> dp(n+1, vector<int>(n + 1,INT_MIN));//dp[i][j] 表示处理完nums的前i个数,化了j秒清0的最大值
		  dp[0][0] = 0;
		  for (int i = 0; i < n; i++){
			  const int index = indexs[i];
			  for (int j = 0; j <= n; j++) {
				  if (INT_MIN == dp[i][j]) { continue; }
				  dp[i + 1][j] = max(dp[i + 1][j], dp[i ][j]);//第i+1个元素不选择
				  if (j < n) {//第i+1个元素选择
					  dp[i + 1][j + 1] = max(dp[i + 1][j + 1], dp[i][j] + nums1[index]+nums2[index]*(j+1));
				  }
			  } 
		  }
		  for (int i = 1; i <= n; i++)
		  {
			  int iMax = 0;
			  for (int j = 0; j <= n; j++)
			  {
				  iMax = max(iMax, dp[j][i]);
			  }
			  if (sum1 + sum2 * i - iMax <= x)
			  {
				  return i;
			  }
		  }
		  return -1;
	  }
  };

测试用例

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> nums1, nums2;
	int x;
	{
		Solution sln;
		nums1 = { 1, 2, 3 }, nums2 = { 1, 2, 3 }, x = 4;
		auto res = sln.minimumTime(nums1, nums2, x);
		Assert(res, 3);
	}

	
}

封装了类

template<class TSave,class TRecord>
class CSingUpdateLineTree
{
public:
	CSingUpdateLineTree(int iEleSize):m_iEleSize(iEleSize), m_vSave(iEleSize*4){

	}
	void Update(int index, TRecord update) {
		Update(1, 1, m_iEleSize, index + 1, update);
	}
	void Query(int leftIndex, int leftRight) {
		Query(1, 1, m_iEleSize, leftIndex + 1, leftRight + 1);
	}
	void Init() {
		Init(1, 1, m_iEleSize);
	}
	const int m_iEleSize;
protected:
	void Init(int iNodeNO, int iSaveLeft, int iSaveRight)
	{
		if (iSaveLeft == iSaveRight) {
			OnInit(m_vSave[iNodeNO], iSaveLeft);
			return;
		}
		const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2;
		Init(iNodeNO * 2, iSaveLeft, mid);
		Init(iNodeNO * 2+1, mid+1, iSaveRight);
		OnUpdateParent(m_vSave[iNodeNO], m_vSave[iNodeNO * 2], m_vSave[iNodeNO * 2 + 1], iSaveLeft, iSaveRight);
	}
	void Query(int iNodeNO, int iSaveLeft, int iSaveRight, int iQueryLeft,int iQueryRight) {
		if (( iSaveLeft >= iQueryLeft) && (iSaveRight <= iQueryRight )) {
			OnQuery(m_vSave[iNodeNO]);
			return;
		}
		if (iSaveLeft == iSaveRight) {//没有子节点
			return;
		}
		const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2;
		if (mid >= iQueryLeft) {
			Query(iNodeNO * 2, iSaveLeft, mid, iQueryLeft, iQueryRight);
		}
		if( mid+1 <= iQueryRight ){
			Query(iNodeNO * 2+1, mid+1, iSaveRight, iQueryLeft, iQueryRight);
		}
	}
	void Update(int iNodeNO,int iSaveLeft,int iSaveRight,int iUpdateNO, TRecord update) {
		if (iSaveLeft == iSaveRight)
		{
			OnUpdate(m_vSave[iNodeNO], update);
			return;
		}
		const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2;
		if (iUpdateNO <= mid) {
			Update(iNodeNO * 2, iSaveLeft, mid, iUpdateNO, update);
		}
		else {
			Update(iNodeNO * 2+1, mid+1, iSaveRight, iUpdateNO, update);
		}
		OnUpdateParent(m_vSave[iNodeNO], m_vSave[iNodeNO * 2], m_vSave[iNodeNO * 2+1],iSaveLeft,iSaveRight);
	}
	virtual void OnInit(TSave& save,int iSave)=0;
	virtual void OnQuery(TSave& save) = 0;
	virtual void OnUpdate(TSave& save, const TRecord& update) = 0;
	virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r,int iSaveLeft,int iSaveRight) = 0;
	vector<TSave> m_vSave;
};

template<class TSave = std::pair<int,int>, class TRecord = int >
class CMyLineTree : public CSingUpdateLineTree<TSave, TRecord>
{
public:
	CMyLineTree(const vector<int>& arr):CSingUpdateLineTree<TSave,TRecord>(arr.size()){
		m_arr = arr;
		const int iMax = *std::max_element(arr.begin(), arr.end());
		m_vIndexs.resize(iMax + 1);
		for (int i = 0; i < arr.size(); i++)
		{
			m_vIndexs[arr[i]].emplace_back(i);
		}
		CSingUpdateLineTree<TSave, TRecord>::Init();
	}
	int Query(int left, int r, int threshold)
	{
		m_vCan.clear();
		CSingUpdateLineTree<TSave, TRecord>::Query(left,r);
		auto [i1, i2] = Query(left, r, m_vCan);
		return (i2 >= threshold) ? i1 : -1;
	}
protected:
	vector<int> m_vCan;
	virtual void OnQuery(TSave& save) override
	{
		m_vCan.emplace_back(save.first);
	}
	virtual void OnUpdate(TSave& save, const TRecord& update) override
	{
		save = { update,1 };
	}
	virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r, int iSaveLeft, int iSaveRight) override
	{
		vector<int> vCan = { left.first,r.first };
		par = Query(iSaveLeft - 1, iSaveRight - 1, vCan);
	}
	std::pair<int, int> Query(int left, int r, vector<int> vCan)
	{
		for (const auto& n : vCan)
		{
			if (-1 == n) {
				continue;
			}
			auto it1 = std::lower_bound(m_vIndexs[n].begin(), m_vIndexs[n].end(), left);
			auto it2 = std::upper_bound(m_vIndexs[n].begin(), m_vIndexs[n].end(), r);
			const int iCnt = it2 - it1;
			if (2 * iCnt > (r - left + 1))
			{
				return { n,iCnt };
			}
		}
		return { -1,0 };
	}
	vector<vector<int>> m_vIndexs;
	vector<int> m_arr;
	virtual void OnInit(TSave& save, int iSave) override
	{
		save = { m_arr[iSave - 1],1 };
	}
};

class MajorityChecker {
public:
	MajorityChecker(vector<int>& arr) :m_lineTree(arr) {

	}

	int query(int left, int right, int threshold) {
		return m_lineTree.Query(left, right, threshold);
	}
	CMyLineTree<> m_lineTree;
};

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关

下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

  • 24
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
好的,以下是基于数组的8个状态的状态机C++源代码: ```c++ #include <iostream> using namespace std; enum State { STATE_1, STATE_2, STATE_3, STATE_4, STATE_5, STATE_6, STATE_7, STATE_8 }; const int NUM_STATES = 8; // State transition table State stateTable[NUM_STATES][NUM_STATES] = { { STATE_2, STATE_4, STATE_1, STATE_6, STATE_5, STATE_3, STATE_7, STATE_8 }, { STATE_6, STATE_1, STATE_2, STATE_8, STATE_3, STATE_5, STATE_4, STATE_7 }, { STATE_4, STATE_3, STATE_2, STATE_1, STATE_7, STATE_8, STATE_6, STATE_5 }, { STATE_5, STATE_6, STATE_7, STATE_8, STATE_1, STATE_2, STATE_3, STATE_4 }, { STATE_7, STATE_2, STATE_3, STATE_4, STATE_5, STATE_1, STATE_8, STATE_6 }, { STATE_3, STATE_5, STATE_4, STATE_2, STATE_8, STATE_6, STATE_1, STATE_7 }, { STATE_8, STATE_7, STATE_6, STATE_5, STATE_4, STATE_3, STATE_2, STATE_1 }, { STATE_1, STATE_8, STATE_5, STATE_7, STATE_6, STATE_4, STATE_3, STATE_2 } }; int main() { State currentState = STATE_1; while (true) { cout << "In state " << currentState + 1 << endl; // Get next state from state table currentState = stateTable[currentState][rand() % NUM_STATES]; } return 0; } ``` 在这个状态机中,我们使用了一个二维数组`stateTable`来存储状态之间的转移关系。该数组的行和列索引都是从0开始,因此我们需要在输出当前状态时将状态值加1。在`while`循环中,我们使用`rand()`函数来生成一个随机数,以从当前状态的转移表中选择下一个状态。需要注意的是,这个状态机是无限循环的,因此需要手动停止程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闻缺陷则喜何志丹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值