洛谷 P1687 机器人小Q 题解

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


入题。

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

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

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

这题不简单!

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

贪心60分求助

算法疑问

是题目问题还是我的问题

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

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

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

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

考虑 i i i j j j 之间关系。

若 i==j

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

f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + ( t [ i − 1 ] [ j − 1 ] + n u m [ i ] > 119 ) ? 1 : 0 f[i][j]=f[i-1][j-1]+(t[i-1][j-1]+num[i]>119)? 1:0 f[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 − 1 j-1 j1 项的状态,到此状态是刚好选择 j j j 项。

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

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

若 i>j

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

(1)不选第 i i i 种能量。

(2)选第 i i i 种能量。

对于(2),在 i = j i=j 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 > j i>j i>j 的情况没考虑完整!若选择和不选择的天数相同呢?

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

还是举个粒子吧。

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

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

对于洛谷上的p1036题目,我们可以使用Python来解决。下面是一个可能的解法: ```python def dfs(nums, target, selected_nums, index, k, sum): if k == 0 and sum == target: return 1 if index >= len(nums) or k <= 0 or sum > target: return 0 count = 0 for i in range(index, len(nums)): count += dfs(nums, target, selected_nums + [nums[i]], i + 1, k - 1, sum + nums[i]) return count if __name__ == "__main__": n, k = map(int, input().split()) nums = list(map(int, input().split())) target = int(input()) print(dfs(nums, target, [], 0, k, 0)) ``` 在这个解法中,我们使用了深度优先搜索(DFS)来找到满足要求的数列。通过递归的方式,我们遍历了所有可能的数字组合,并统计满足条件的个数。 首先,我们从给定的n和k分别表示数字个数和需要选取的数字个数。然后,我们输入n个数字,并将它们存储在一个列表nums中。接下来,我们输入目标值target。 在dfs函数中,我们通过迭代index来选择数字,并更新选取的数字个数k和当前总和sum。如果k等于0且sum等于target,我们就找到了一个满足条件的组合,返回1。如果index超出了列表长度或者k小于等于0或者sum大于target,说明当前组合不满足要求,返回0。 在循环中,我们不断递归调用dfs函数,将选取的数字添加到selected_nums中,并将index和k更新为下一轮递归所需的值。最终,我们返回所有满足条件的组合个数。 最后,我们在主程序中读入输入,并调用dfs函数,并输出结果。 这是一种可能的解法,但不一定是最优解。你可以根据题目要求和测试数据进行调试和优化。希望能对你有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值