如何更快更准确地解决这个问题?(关于IT技术)

3

问题如下:

拉姆是一个懒惰的农民。他继承了他父亲相当大的农场和一座漂亮的房子。拉姆把农田租给别人,赚取相当可观的收入。他的父亲过去曾在家里养一只水牛并出售它的牛奶,但水牛在他父亲去世后几天就死了。
拉姆也想通过养牛赚点钱,但方法与众不同。他决定自己未来的发展在于对牛进行投机。在他的村里,每天都有人买卖水牛。价格随着一年的变化而波动,但在任何一天,价格总是相同的。
他决定在价格低时购买水牛,在价格高时出售水牛,并在此过程中积累巨大财富。不幸的是,他的房子只能容纳一头水牛,因此他最多只能拥有一头水牛。
在进入水牛市场之前,他决定检查最近几天水牛价格的变化情况,并确定他可能获得的最大利润。假设在过去的10天里,一头水牛的价格变化如下:

10 12 8 11 11 10 12 15 13 10

拉姆是一个懒人,他估计在过去的10天里他最多愿意参观市场5次(每次买或卖一头水牛)。鉴于此,他可能获得的最大利润为9卢比。要实现这一点,他在第1天购买一头水牛,在第2天出售它,在第3天再买一头,第8天出售。如果他不那么懒惰,愿意参观市场6次,那么他可以赚更多的钱。他可以在第1天买入,第2天卖出,在第3天买入,在第4天卖出,在第6天买入,并在第8天卖出以获得10卢比的利润。
你的任务是帮助拉姆计算他能够在投机水牛上获得的最大收益,给定一个期间内每日水牛价格的历史记录以及拉姆在此期间内最多访问牲畜市场的限制。

输入格式
输入的第一行包含两个整数N和K,其中N是可用价格数据的天数,K是拉姆在此期间内最多愿意访问牲畜市场的次数。接下来的N行(第2、3, ..., N+1行)每行包含一个正整数。第i+1行上的整数,1 ≤ i ≤ N,表示第i天一头水牛的价格。

输出格式
一个非负整数,表示拉姆在最多访问K次市场的情况下能够获得的最大利润。
您可以假设N≤400,K≤400。
时间限制= 3秒。

如果没有限制k,我们可以使用贪心算法解决这个问题,

 while c < N
   i = c
   while price[day i] > price[day i+1] increment i;
   j = i+1
   while price[day j] < price[day j+1] increment j;
   add price[day j] - price[day i] to out profit
   c = j+1

但是我们不能在这里使用它,因为是否访问特定的一天取决于我们访问了多少天。

以下是我尝试过的:

 //Store all the pairs generated in the previous algorithm in a linked list L, each
 //element has two attributes buy,sell
 while length of L > k/2
       find an element i in the list such the (L[i].sell- L[i-1].buy) - 
       (L[i-1].buy - L[i-1].sell) - (L[i].buy - L[i].sell) is maximized.
       Then set L[i-1].sell to L[i].sell and delete i from the list

这是一个在线评测问题,当我提交时,有些测试用例超时,有些错误,只有一个测试用例正确。
我的方法有什么问题?它为什么会慢,因为它需要约O(NK)的时间,其中N和K < 400。如何改进算法以正确解决问题?
编辑:这不是作业,我在这里找到了这个问题:http://opc.iarcs.org.in/index.php/problems/BUFFALOES

@niklas-r 这不是一道作业题,我在一个在线评测网站上看到了这个问题:http://opc.iarcs.org.in/index.php/problems/BUFFALOES - 2147483647
@NiklasR,“homework”是一个已弃用的标签,即使它没有被弃用,你也没有理由添加它。 - Jim Balter
2
提示:最佳解决方案仅花费了1毫秒。 - Jim Balter
1个回答

2

我没有仔细分析,但是我觉得你对于懒惰的农民的想法有点贪心。我很难想象你的链表或者链表上的操作。

我认为一个好的思考方式是将其转化为一个图形,不用担心效率问题,其中每一天都是图中的一个节点。

如果我们有一个勤劳的交易员愿意尽可能经常地访问市场,那么它看起来像下面的图1,我已经采取了你的例子的前几天。弧从图中的每一天指向图中的每一个后续天,并且弧的权重如下:

  • 连续的天数必须有一条弧——或者加上在第二天买入然后卖出的收益的权重,或者(如果这样做会亏损)权重为零
  • 非连续的天数仅在利润为正时需要一条弧。(虽然你可以画出非盈利对的零权重弧,但我在下面抑制了它们,以便图形可读性更好。)

创建这个图需要O(N^2)的比较/减法。一旦你有了这个图,找到最好的农民计划就相当于找到图中最长的路径(例如,从第一天到最后一天的弧值之和最大的路径)。通常,在图中找到最长的路径是NP-Complete问题,但在这种情况下,我们有一个有向无环图——你可以简单地否定所有边权并使用Dijkstra算法在多项式时间内找到最短路径。

Figure 1: Non-Lazy Farmer

为了处理懒惰的农民,你需要调整这个图形结构,使它“计算”非零弧。我们通过使图形变得更大来实现这一点。如果农民愿意去市场k次,他就有floor(k/2)次买卖机会。让我们称这个数字为X,并将我们的图形节点每个X+1次绘制。

同一行中的每个连续弧(无论当天是否盈利)都具有0的权重。长度为正数的弧被重定向到下一行。如果农民愿意去市场4次,总共有2次买卖机会,则图2显示了这种情况。你还需要添加一个虚拟的“终端节点”,你可以从每一行的末尾到达它,而不需要花费任何代价。

你可以看到,这样做可以通过确保每次赚钱的机会从一行移动到另一行,并且永远不会有使用同一行超过一次的机会来“计算”盈利弧。所以你可以找到最长的路径来找到正确的答案;而且图形是有向无环的,因此可以将其转换为最短路径问题并在多项式时间内解决。

坏消息是,节点和弧的数量显著增加。如果k=N,则可能有O(N^2)个节点,而不是N个节点。同样,弧的数量也从O(N^2)增加到了O(N^3)。
您可以将问题视为一个网格来更好地利用时间和空间,类似于最长公共子序列问题,但至少这说明了问题的结构。
图2:懒惰农民

(作为一个旁注,这个问题被标记为“动态规划”,就我而言,Dijkstra的最短路径算法可以视为动态规划。) - Novak
非常感谢您的回答,我还找到了另一个算法,类似于递归但是它排除了一些可能性,最终我成功通过了测试。 - 2147483647
你愿意分享算法吗? - Novak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接