使用动态规划在资金有限的情况下选择活动

Posted

技术标签:

【中文标题】使用动态规划在资金有限的情况下选择活动【英文标题】:Activity Selection with Limited Money using Dynamic Programming 【发布时间】:2018-09-10 10:28:13 【问题描述】:

我正在学习动态编程,我解决了几个 DP 问题,但我发现这一个对于我的水平来说是相当困难的。对我来说,这个问题比简单的活动选择要困难得多。 所以,给定 N 个活动,每个活动都有 Cost,选择最大活动,你不能花费超过 M 的钱。 1 1 1 例如,我们有 5 个活动和 11 个金钱 格式:From-To -> Cost 1-3 -> 5 1-5 -> 9 4-6 -> 6 5-6 -> 1 6-10 -> 1 1-5、5-6、6-10 = 9 + 1 +1 = 11 因此,答案是 3 个活动 当然,如果有另一个相同最大活动量的答案:1-3、5-6、6-10,那么您可以选择任何您想要的答案。 这是我的代码:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct temp
    int from;
    int to;
    int cost;
;
using data = vector<temp>;

int f(data a, int len, int m)
    if (len<0 || m<0) return 0;
    int val=0;
    for (int i=0; i<len; i++)
        if (a[len].from >= a[i].to)
            val=max(val,f(a,len-i-1,m-a[len].cost)+1);
        else
            val=max(val,f(a,len-i-1,m)+1);//try another activity
    
    return val;



int main()
    data activity;
    int n,m;
    cin >>n >> m;
    for (int i=0; i<n; i++)
        int a,b,c;
        cin >> a >> b >> c;
        activity.push_back(a,b,c);
    
    cout << f(activity,activity.size()-1,m);

我的代码有什么问题?我知道有几件事是错的,但我不知道在哪里。如何修复递归中的错误? 另外,如果可能的话,你能让算法更快吗?无需更改为自下而上的方法

【问题讨论】:

请花点时间阅读Why should I not #include <bits/stdc++.h>? 好吧,我刚刚修好了。 请指定问题的约束条件。图的大小、边数、金额等... 您是否只寻找 dp 解决方案?也正如上面 Pham 所提到的,您能否为上述问题提供约束? 好的,我只提供约束。 【参考方案1】:

我觉得这是加权活动问题和背包问题的混合问题。 这很困难,但非常有趣,也是一个很好的学习机会。 我提议一个DP。方法在下面的帖子中。 如果我错了,请有人纠正我。 那我希望有建设性的讨论。


方法

首先,我们将活动标记为A = a1, a2, .., aN,并按照完成时间对其进行排序,如下所示:

  |----|a1
     |-----|a2
       |-------|a3 
           |-----|a4
                 ....

接下来,

S(A,M)成为活动A的最佳解决方案M

L(a)成为A的所有活动,严格在元素a的左边 A.

example.)
            |----|a1
               |-----|a2          L(a4)=a1, a2
                 |-------|a3 
                     |-----|a4
                           ....

我们可以从最左边的活动a1 开始搜索S(A,M),然后搜索最右边的活动aN。 对于每个活动ai,我们可以考虑其左侧区域L(ai)

如果M是无限的,在i-th step中,S(L(ai),M)ai的并集是一个候选最优解。因此子问题是S(L(ai),M)。 在这一步中,我们已经有了S(a1,M),S(a1,a2,M),...,S(a1,...,a_i-1,M)。 由于a1,...,aN 是按完成时间排序的,因此我们可以将S(L(ai),M) 作为其中之一或其中一些。 这种递归方法是当前问题的起点。

  example.) M=infinity

  |----|(a1,cost=2)          subproblem of 1st step: S(L(a1),M)=empty
       |---|(a2,1)           subproblem of 2nd step: S(L(a2),M)=a1
           |----|(a3,2)      subproblem of 3rd step: S(L(a3),M)=a1,a2
           |--------|(a4,4)  subproblem of 4th step: S(L(a4),M)=a1,a2=S(L(a3),M)
                   ....                                         ^^^^^^^^^^ cached!

如果M 是有限的,则S(L(ai),M-cost(ai))ai 的并集是最优解的候选。因此我们必须解决的不是S(L(ai),M),而是S(L(ai),M-cost(ai))。 在这种情况下,每个步骤中的子问题不一定被缓存。

  example.) M=5

  |----|(a1,2)               subproblem of 1st step: S(L(a1),3)=empty
       |---|(a2,1)           subproblem of 2nd step: S(L(a2),4)=a1
           |----|(a3,2)      subproblem of 3rd step: S(L(a3),3)=a1,a2
           |--------|(a4,4)  subproblem of 4th step: S(L(a4),1)=a2!=S(L(a3),3)
                   ....                                         ^^^^^^^^ uncached!

因此,在具有无限M 的标准加权活动选择问题的情况下,我们始终可以专注于最活跃的活动组合。 但在当前有限M的问题中,我们必须在递归步骤中缓存成本低于M的各种组合。


实施

记忆

适当的缓存表结构根据M是否无限而不同。 如果M 是无限的,一维缓存表就足以执行动态规划。 但是如果M 是有限的,我们需要二维缓存表。在您的示例中,它变为如下:

                                         end
            3     4     5        6                   10 <= end
       1 |     |     |     |   [5,6]   |            [5,6],[6,10]             |
       2 |     |     |     |   [5,6]   |            [5,6]+[6,10]             |
money  3 |     |     |     |   [5,6]   |            [5,6]+[6,10]             |
       4 |     |     |     |   [5,6]   |            [5,6]+[6,10]             |
       5 |[1,3]|[1,3]|[1,3]|[1,3],[5,6]|            [5,6]+[6,10]             |
       6 |[1,3]|[1,3]|[1,3]|[1,3]+[5,6]|[1,3]+[5,6],[1,3]+[6,10],[5,6]+[6,10]|
      ...  ...

因此我定义了如下缓存表类CacheTable

缓存表std::map&lt;std::pair&lt;to,money&gt;,Activities&gt;&gt; table_缓存S(a|a of A, a&lt;=to,money)的元素。

CacheTable::operator()用于访问缓存表。

CacheTable::getCachedOptimal 用于在完成时间和金钱的限制下从缓存结果中找到最佳解决方案。

我的快速实现如下:

struct activity

    std::size_t from;
    std::size_t to;
    std::size_t cost;
;

using Activities = std::vector<activity>;

class CacheTable

    // (to,money) --> cached optimal activities
    std::map<std::pair<std::size_t,std::size_t>, Activities> table_;

public:
    Activities& operator()(std::size_t to, std::size_t money)
    
        auto key = std::make_pair(to, money);
        auto it = table_.find(key);

        if(it == table_.cend())
            it = table_.emplace_hint(it, std::move(key), Activities());
        

        return it->second;
    

    Activities getCachedOptimal(std::size_t to, std::size_t money) const
    
        Activities cachedOptimal;

        for(auto it = table_.begin(); it != table_.cend(); ++it)
        
            if((it->first.first <= to) && (it->first.second <= money))
            
                if(it->second.size() > cachedOptimal.size())
                    cachedOptimal = it->second;
                
            
        

        return cachedOptimal;
    
;

求解器

使用上面的缓存表,我们可以实现一个DP。当前问题的求解器如下。 此求解器只能找到 S(A,M) 的单个元素。 DEMO is here. 虽然这应该可行,但我认为有更有效的方法来解决这个问题。

class DPSolver

    Activities activities_;
    mutable CacheTable cacheTable_;

    Activities impl(std::size_t from, std::size_t to, std::size_t money) const
    
        if((from >= to) || (money == 0))
            return ;
        

        const auto cache = cacheTable_(to, money);
        if(!cache.empty())
            return cache;
        

        for(const auto& act_i : activities_)
                    
            if((act_i.to <= to) && (money >= act_i.cost))
            
                const auto remaining = (money - act_i.cost);

                auto acts = impl(from, act_i.from, remaining);
                const auto thisCost = calcCost(acts) + act_i.cost;

                const auto found = cacheTable_.getCachedOptimal(act_i.to, thisCost);

                if(found.size() < (acts.size()+1))
                
                    acts.push_back(std::move(act_i));
                    cacheTable_(act_i.to, thisCost) = acts;
                
            
        

        return cacheTable_.getCachedOptimal(to, money);
    

public:
    DPSolver(const Activities& activities) : activities_(preprocess(activities))
    

    static Activities preprocess(const Activities& activities)
    
        auto sorted(activities);

        // preprocessing to order by finished time "to".
        std::sort(
            sorted.begin(), sorted.end(),
            [](const activity& al, const activity& ar)
             return std::tie(al.to, al.from, al.cost) < std::tie(ar.to, ar.from, ar.cost); );

        return sorted;
    

    static std::size_t calcCost(const Activities& activities)
    
        return std::accumulate(
                activities.cbegin(), activities.cend(), 0,
                [](int sum, const activity& a) return sum + a.cost;);
    

    Activities operator()(std::size_t from, std::size_t to, std::size_t money) const
    
        // clear cache table
        cacheTable_ = CacheTable();

        return impl(from, to, money);
    
;

【讨论】:

以上是关于使用动态规划在资金有限的情况下选择活动的主要内容,如果未能解决你的问题,请参考以下文章

活动选择的贪心算法与动态规划(未完成)

宏观动态规划框架下的企业活动

左程云-递归和动态规划

动态规划基础-----01背包(总结)

动态规划算法

第三章 动态规划-基于模型的RL