首先需要使用贪心思想,考虑复习的顺序,顺序应该按照遗忘度 c_i 从大到小排序。如果很大的 c_i 与很大的 j 相乘,乘积会很大。
然后就用分治好了,注意到 n 大约是 40 直接枚举是不行的,因此考虑折半搜索的思想,我们把知识点分成两个部分,设前一半是 A 后一半是 B,分别枚举两个部分,然后把结果合并。
首先枚举前一半 A,我们对 A 的每个子集,记录选择元素个数 k_1、总时间 time 和总价值 value。
然后我们枚举后一半 B,对 B 的每个子集,记录选择元素个数 k_2、基础时间 time_2、遗忘度总和 sum 和总价值 value_2。合并结果的时候,B 的总时间为 time_2+(m-k_2)\times sum。
然后我们要来合并答案,对于每个可能的 k_2,我们计算 k_1=m-k_2。遍历 B 的所有子集,在 A 的对应状态中查找满足总时间要求的最大价值。
上个伪代码:
/*输入*/
sort(&item[0],&item[n],cmp);//按照遗忘度排序
int n1=n/2,n2=n-n1;//分成两段
vector<vector<pair<int,int> > >p(n1+5);//用来记录每种子集的状态
for(int mask=0;mask<(1<<n1);mask++){//生成这一段的所有子集
int k=__builtin_popcount(mask);//这是一个内置函数用于统计二进制码中 1 的个数,这里是记录下可能的 k 的值
int t_=0,v_=0,cnt=0;//初始化总时间,总价值,元素个数
for(int i=0;i<n1;i++){//枚举前半段的所有元素
if(mask&(1<<i)){//如果选择了第 i 个元素
//更新元素个数
//更新时间
//更新价值
}
}
// 记录下这个子集的状态
}
/*这里是枚举后半段和前面大致相同,自己试试看吧!*/
vector<vector<pair<int,int> > >list(n1+5);
for(int k=0;k<=n1;k++){
if(p[k].empty())continue;//如果没有这种状态跳过
/*对 p 进行排序*/
vector<pair<int,int>>v;
for(auto it:p[k])if(v.empty()||it.second>v.back().second)v.push_back(it);
list[k]=v;
}
int ans=-1;//答案
for(int k2=0;k2<=min(m,n2);k2++){
int k1=m-k2;//计算 k1
if(k1<0||k1>n1)continue;
if(list[k1].empty())continue;
for(auto it:p_[k2]){
//按照上面说的公式计算
if(t_>t)continue;
int t0=t-t_;
auto v=list[k1];
auto it_=upper_bound(v.begin(),v.end(),make_pair(t0,LLONG_MAX));
if(it_==v.begin())continue;
it_--;
int tot=it_->second+v_;
if(tot>ans)ans=tot;
}
}
/*输出*/