我正在学习动态编程,我解决了一些DP问题,但是我发现这对我的水平来说相当困难。对我来说,这个问题比简单的活动选择要困难得多。
因此,如果给定N个活动且每个活动中都包含“费用”,则选择最大活动数,则您的支出不能超过M金额。
1 <= N,M <= 10 ^ 5
1 <=开始<=结束<= 10 ^ 5
1 <=成本<= 10 ^ 5
例如,我们有5个活动和11个钱
格式:
从-到->费用
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);
}
我的代码有什么问题?我知道有几件事是错的,但我不知道在哪里。如何修复递归中的错误? 另外,如果可能,您可以使算法更快吗?无需更改自下而上的方法
答案 0 :(得分:1)
我觉得这是加权活动问题和背包问题的混合问题。 这很困难,但非常有趣,并且是一个很好的学习机会。 我提议一个DP。以下帖子中的方法。 如果我弄错了什么,请有人纠正我。 然后,我希望进行建设性的讨论。
首先,我们将活动标记为A = {a1, a2, .., aN}
,并按如下所示的时间整理活动:
|----|a1
|-----|a2
|-------|a3
|-----|a4
....
下一步
让S(A,M)
是活动A
的{{1}}活动的最佳解决方案。
让M
严格位于元素L(a)
左侧,A
的所有活动
的a
。
A
我们可以从最左边的活动example.)
|----|a1
|-----|a2 L(a4)={a1, a2}
|-------|a3
|-----|a4
....
开始搜索S(A,M)
,然后进行最右边的活动a1
。
对于每个活动aN
,我们可以考虑其左侧区域ai
。
如果L(ai)
是无限的,则在第M
步中,i
和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
求解器
使用上面的缓存表,我们可以实现DP。当前问题的求解器如下。
该求解器只能找到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;
}
};
的单个元素。
DEMO is here.
尽管这应该可行,但我认为有解决此问题的更有效方法。
S(A,M)