一、问题描述
拓展:假如每个活动都有收益,收益各不相同,问怎样使收益最大呢?
二、求解过程分析
重要数据结构:
int p[N]; //p[i]代表在ai开始前最后结束的活动
int rec[N]; //记录决策
double dp[N]; //动态规划数组,dp[i] 代表选择第i个活动所获得的收益
//dp[p[i]] 代表 选择该活动上一个最先结束的活动 时所获得的收益
//状态转移方程为:dp[i] = max(dp[p[i]] + actList[i].value, dp[i - 1])
-
已证明优先选择最早结束的活动时举办活动的场次最多,证明略。那我们只需将各活动按照结束时间升序排序(使用快速排序),然后将第一个活动加入集合S(表示选择的活动),因为第一个活动一定最早结束的,再依次比较下一个活动的开始时间是否大于当前选择活动的结束时间:
struct actTime S[N]; //选择的活动集合 int res = 1; //活动场数 int k = 1; //当前活动 S[1] = actList[1]; //将第一个活动加入已选择活动集合 for(int i = 2; i <= n; i ++){ if(actList[i].startTime >= actList[k].endTime){ //该场的开始时间大于上一场的结束时间 S[++ res] = actList[i]; //将该场活动加入集合 k = i; //更新当前的活动场次 } }
-
使用二分法查找第i个活动前最后结束的活动
p[1] = 0; //第一个活动前没有活动 for(int i = 2; i <= n; i ++){ //依次查找活动的p[i] int time = actList[i].startTime; int l = 1, r = n; while(l < r){ int mid = l + r + 1 >> 1; if(actList[mid].endTime < time) l = mid; else r = mid - 1; } //找到后赋值 p[i] = r; //l 和 r 都可 }
-
动态规划
对于第i个活动,我们的选择有 选 与 不选
选:
dp[i] = dp[p[i]] + actList[i].value
不选:
dp[i] = dp[i - 1]
for(int i = 1; i <= n; i ++){ if(dp[p[i]] + actList[i].value > dp[i - 1]){ //选择 dp[i] = dp[p[i]] + actList[i].value; rec[i] = 1; //记录决策 选择 } else{ //不选 dp[i] = dp[i - 1]; } }
三、算法步骤
测试用例
11
1 4 7 3 5 6 0 8 9 5 7 9 3 9 10 5 9 6 6 10 19 8 11 5 8 12 8 2 14 21 12 16 14
完整代码:
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
/**
//求场地最多能举办几场活动,分别是哪些活动
1.先用将活动按结束时间进行升序排序 -- 归并排序
//拓展:求如何选择活动带来的收益最大
2.二分查找法求解p[i]
3.再用动态规划选择活动
*/
const int N = 10010;
int n; //活动个数
int p[N]; //p[i]代表在ai开始前最后结束的活动
int rec[N]; //记录决策
double dp[N]; //动态规划数组,dp[i] 代表选择第i个活动所获得的收益
//dp[p[i]] 代表 选择该活动上一个最先结束的活动 时所获得的收益
//状态转移方程为:dp[i] = max(dp[p[i]] + actList[i].value, dp[i - 1])
//活动时间数据结构
struct actTime{
double startTime; //活动开始时间
double endTime; //活动结束时间
double value = 0; //活动的收益
void swap(actTime &a){ //交换函数
actTime b;
b.endTime = this->endTime;
b.startTime = this->startTime;
b.value = this->value;
this->endTime = a.endTime, this->startTime = a.startTime, this->value = a.value;
a.endTime = b.endTime, a.startTime = b.startTime, a.value = b.value;
}
};
struct actTime actList[N]; //活动信息列表
//快速排序
void quick_sort(actTime *actList, int l, int r)
{
if(l >= r) return;
//x:选取一个作哨兵
int x = actList[l].endTime;
int i = l - 1, j = r + 1;
//将 >= actList[l].endTime的都放在右边
//将 <= actList[l].endTime的都放在左边
while(i < j){
do{
i ++;
}while(actList[i].endTime < x);
do{
j --;
} while(actList[j].endTime > x);
if(i < j) actList[i].swap(actList[j]);
}
quick_sort(actList, l, j);
quick_sort(actList, j + 1, r);
}
//主函数
int main()
{
//输入
cout << "输入活动个数:";
cin >> n;
cout << "输入各活动的开始时间和结束时间及收益:";
for(int i = 1; i <= n; i ++){
cin >> actList[i].startTime >> actList[i].endTime >> actList[i].value;
}
cout<<endl;
//归并法 按照活动结束时间 进行升序排序
quick_sort(actList, 1, n);
cout << endl << "--------------- 求最多活动场数 ---------------"<<endl;
//直接输出场地最多能举办活动的个数
struct actTime S[N]; //选择的活动集合
int res = 1; //活动场数
int k = 1; //当前活动
S[1] = actList[1]; //将第一个活动加入已选择活动集合
for(int i = 2; i <= n; i ++){
if(actList[i].startTime >= actList[k].endTime){ //该场的开始时间大于上一场的结束时间
S[++ res] = actList[i]; //将该场活动加入集合
k = i; //更新当前的活动场次
}
}
cout << "最大活动个数为:" << res << endl;
for(int i = 1; i <= res; i ++){
cout << "第" << i << "场开始时间和结束时间:" <<S[i].startTime <<"\t" << S[i].endTime << endl;
}
cout<<endl;
//扩展:最大收益
//1. 用二分法找到p[i]
//p[i] 代表第ai个活动前最后结束的活动bi, 需要比较的是 bi.endtime 和 ai.starttime
//2. 动态规划
cout << endl << "--------------- 求最大出租收益 ---------------"<<endl;
//二分查找:最后结束 找的是 右端点
p[1] = 0; //第一个活动前没有活动
for(int i = 2; i <= n; i ++){ //依次查找活动的p[i]
int time = actList[i].startTime;
int l = 1, r = n;
while(l < r){
int mid = l + r + 1 >> 1;
if(actList[mid].endTime < time) l = mid;
else r = mid - 1;
}
//找到后赋值
p[i] = r; //l 和 r 都可
}
//动态规划求最大收益
for(int i = 1; i <= n; i ++){
if(dp[p[i]] + actList[i].value > dp[i - 1]){ //选择
dp[i] = dp[p[i]] + actList[i].value;
rec[i] = 1; //记录决策 选择
} else{ //不选
dp[i] = dp[i - 1];
}
}
//输出
cout << "活动最大收益为:" << dp[n] << endl;
cout << "对应选择方案为:" <<endl;
k = n;
while(k > 0){
if(rec[k] == 1){
cout << "选择活动" << k << "\t第" << k << "场开始时间和结束时间:" <<actList[k].startTime <<"\t" << actList[k].endTime <<
"\t收益为:" << actList[k].value << endl;
k = p[k];
}else{
k = k -1;
}
}
// //测试
// for(int i = 1; i <= n; i ++){
// cout << actList[i].endTime << "\t" << p[i] << endl;
// }
return 0;
}
运行截图:
四、复杂性分析
快速排序:O(nlogn)
二分查找:O(nlogn)
动态规划:O(n)
输出方案:O(n)
时间复杂度为:O(nlogn)