期末 T7 题解

首先需要使用贪心思想,考虑复习的顺序,顺序应该按照遗忘度 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;
        }
    }
    /*输出*/
2 个赞

注意到这个帖子编号为39999

1 个赞

编号咋看