洛谷 P1687 机器人小Q 题解

动态规划充电
本文通过动态规划解决了一道关于充电的问题,旨在找到使用特定数量的能量值为设备充电所需的最少天数。文章详细阐述了状态转移方程的设计过程,并通过比较不同方案优化了最终的算法实现。

我看这题似乎没有将状态转移方程讲清楚的题解啊,所以我来了


入题。

看到这题,我立马想到贪心

对题意,我的错误的理解为:对于任意一个能量菜单上的能量值都可以在任意时间充电。这样,就可以直接排序选前kkk个最小值进行充电。

然后,我看到了此题的颜色:普及+/提高

这题不简单!

于是,我就看到了讨论中的:

贪心60分求助

算法疑问

是题目问题还是我的问题

哦原来是 按一定顺序 给出了 NNN 个单位的能量值,使用也得 按一定顺序 。也就是说,用了第iii个后,第iii个之前的就不能再用了。

好了,排序不能用了,决策题只能考虑动态规划了。

于是设f[i][j]f[i][j]f[i][j]表示在第iii个之前使用了jjj种能量值时最少用的天数。

比如说对于样例,f[3][2]f[3][2]f[3][2]表示第三个能量值之前的三种能量值1,119,119中要选择两个能量值并充电给小Q。

考虑i和j之间关系。

若 i==j

i==ji==ji==j,也就是说前iii个一定要全部选,那么第iii项也一定要选。那么就考虑选择第iii个:

f[i][j]=f[i−1][j−1]+(t[i−1][j−1]+num[i]>119)?1:0f[i][j]=f[i-1][j-1]+(t[i-1][j-1]+num[i]>119)? 1:0f[i][j]=f[i1][j1]+(t[i1][j1]+num[i]>119)?1:0

看起来那么长,其实就是将一个if嵌在其中。

首先,f[i−1][j−1]f[i-1][j-1]f[i1][j1]指前一个选择了j−1j-1j1项的状态,到此状态是刚好选择jjj项。

而后面的一个三元运算,则指是否需要重新的一天来充电——用题目的话来说就是充电量超过了119,则要用另一天来充电。否则小Q就要原地爆炸

注:t数组保存着在第i,j状态时那一天已经用了多少能量;

若 i>j

再考虑i>ji>ji>j。这表示在前iii种能量值中只用选择j个,而并不用全部选完。这就又分两种情况:

(1)不选第i种能量。

(2)选第i种能量。

对于(2),在i==j时我们已经讨论过了,那么只需要Ctrl-C+V就好了。

对于(1),直接f[i][j]=f[i−1][j]f[i][j]=f[i-1][j]f[i][j]=f[i1][j]就得了,没啥要考虑的。

只要对比(1)和(2)的大小就OK了。

依照上面的思路,我们可以写出这个简单的代码。

(不要着急复制粘贴AC这道题啊,因为下面的代码只有30分)

#include<bits/stdc++.h>
using namespace std;
int num[3001],f[3001][3001],t[3001][3001];

int main()
{
	int n,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>num[i];
	for(int i=1;i<=n;i++) f[i][0]=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
		{
			if(i==j)
			{    //选取当前能量
				f[i][j]=f[i-1][j-1];
				t[i][j]=t[i-1][j-1]+num[i];
				if(t[i][j]>119)
				{
					t[i][j]=num[i];
					f[i][j]++;
				}
			}
			else
			{
				if(f[i-1][j]<f[i-1][j-1]+(t[i-1][j-1]+num[i]>119)? 1:0)
				{    //不选取当前能量
					f[i][j]=f[i-1][j];
					t[i][j]=t[i-1][j];
				}
				else
				{    //选取当前能量
					f[i][j]=f[i-1][j-1];
					t[i][j]=t[i-1][j-1]+num[i];
					if(t[i][j]>119)
					{
						t[i][j]=num[i];
						f[i][j]++;
					}
				}
			}
		}
	cout<<f[n][k]+(t[n][k]!=0)? 1:0; //此处要将最后一天算上,f[n][k]并没有将当时所用的天数包含在内,即在使用的第一天f[i][j]还是0
	return 0;
}

好,为什么30分呢?等等,i>ji>ji>j的情况没考虑完整!若选择和不选择的天数相同呢?

先停一下,看看f[i][j]f[i][j]f[i][j]的含义。它表示在第iii个之前使用了jjj种能量值时最少用的天数(的向下取整)。对啊!这使当前这一天的使用的能量没有发挥作用啊!

还是举个粒子吧。

现在已经使用了这样的能量组合:

118 1 50

到第三天时,用了1天并还剩下50的能量。剩下的50能量便是主要要对比的对象了。

那么,天数相同时,最后一天所用的能量肯定越少要好啊!所以,在天数相同的情况下,就选择最后一天使用能量较少得那一种方案。

于是代码变成了这样:

#include<bits/stdc++.h>
using namespace std;
int num[3001],f[3001][3001],t[3001][3001];

int main()
{
	int n,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>num[i];
	for(int i=1;i<=n;i++) f[i][0]=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
		{
			if(i==j)
			{
				f[i][j]=f[i-1][j-1];
				t[i][j]=t[i-1][j-1]+num[i];
				if(t[i][j]>119)
				{
					t[i][j]=num[i];
					f[i][j]++;
				}
			}
			else
			{
				if(f[i-1][j]<f[i-1][j-1]+(t[i-1][j-1]+num[i]>119)? 1:0)
				{
					f[i][j]=f[i-1][j];
					t[i][j]=t[i-1][j];
				}
				else if(f[i-1][j]==f[i-1][j-1]+(t[i-1][j-1]+num[i]>119)? 1:0)    //此处考虑相同的情况
				{
					if(t[i-1][j]<=((t[i-1][j-1]+num[i])>119? num[i]:t[i-1][j-1]+num[i]))     //这里注意三元运算的优先级
					{
						f[i][j]=f[i-1][j];
						t[i][j]=t[i-1][j];
					}
					else
					{
						f[i][j]=f[i-1][j-1];
						t[i][j]=t[i-1][j-1]+num[i];
						if(t[i][j]>119)
						{
							t[i][j]=num[i];
							f[i][j]++;
						}
					}
				}
				else
				{
					f[i][j]=f[i-1][j-1];
					t[i][j]=t[i-1][j-1]+num[i];
					if(t[i][j]>119)
					{
						t[i][j]=num[i];
						f[i][j]++;
					}
				}
			}
		}
	cout<<f[n][k]+(t[n][k]!=0)? 1:0;
	return 0;
}

完结。


等等?为什么没有“You can’t do it.”都可以的?

哈哈哈,这说明了一些显而易见事实

既然想到了那么就说一说怎么判断吧。如何凑出不可能充k种电的情况呢?

只有一种情况,就是没有足够的方式充电。

那么输入就可以改成这样:

int cnt=0;
for(int i=1;i<=n;i++)
	cin>>num[i],cnt+=(num[i]<=119);
if(cnt<k) puts("You can't do it."); //没有足够的种类去充电

正式完结。


有不懂得问题,问我哈

有错误请指出,方便我改进自身qwq

P3922 题目《改变队列》(暂译名,具体题目请以平台为准)是一道涉及队列操作与数据结构设计的题目。题目的核心要求是通过一系列操作来实现队列中元素的调整,以满足特定条件。 ### 问题分析 题目通常要求维护一个队列,支持以下操作: 1. **入队**:将一个元素加入队列末尾。 2. **出队**:移除队列头部的元素。 3. **查询队列中某个特定位置的元素**。 4. **对队列中的元素进行某种形式的修改**(如更新某个位置的值)。 这类问题通常需要一个既能高效进行队列操作又能支持随机访问的数据结构。标准库中的 `std::queue` 并不能很好地支持随机访问,因此可以考虑使用 `std::deque` 或者 `std::vector` 来实现更灵活的操作。 ### 解题思路 - **数据结构选择**:使用 `std::deque` 或 `std::vector` 来模拟队列,因为它们支持高效的头部和尾部操作,同时也能通过索引进行随机访问。 - **操作实现**: - 入队操作直接使用 `push_back()`。 - 出队操作可以通过维护一个偏移量或使用 `pop_front()` 来实现。 - 查询特定位置的元素可以通过索引直接访问。 - 修改操作也可以通过索引直接完成。 ### 示例代码 以下是一个基于 `std::vector` 的实现示例: ```cpp #include <iostream> #include <vector> using namespace std; int main() { int q; cin >> q; vector<int> queue; int front = 0; // 模拟队列的头部位置 for (int i = 0; i < q; ++i) { string op; cin >> op; if (op == "push") { int val; cin >> val; queue.push_back(val); } else if (op == "pop") { front++; // 模拟出队 } else if (op == "query") { int pos; cin >> pos; cout << queue[front + pos - 1] << endl; } else if (op == "modify") { int pos, val; cin >> pos >> val; queue[front + pos - 1] = val; } } return 0; } ``` ### 复杂度分析 - **入队和出队**:时间复杂度为 $O(1)$。 - **查询和修改**:由于使用了随机访问,时间复杂度也为 $O(1)$。 ### 优化建议 - 如果数据量较大,可以考虑使用 `std::deque`,它在头部和尾部的插入和删除操作效率更高。 - 对于频繁的中间位置访问和修改操作,`std::vector` 仍然是一个不错的选择,因为它的内存是连续的,访问速度较快。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值