使用动态规划在资金有限的情况下选择活动
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<std::pair<to,money>,Activities>> table_
缓存S(a|a of A, a<=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);
;
【讨论】:
以上是关于使用动态规划在资金有限的情况下选择活动的主要内容,如果未能解决你的问题,请参考以下文章