题目
解法一:枚举
枚举是很重要的基础解法,遍历每一种结果直到满足你设置的边界条件为止
在本题中,枚举从k开始由小到大的天数,判断缩短至该天时m的资源是否足够,如果能满足k就输出k并结束,不能满足就放宽要求(增加截止日期)直到资源缩短到某一天不够用为止。
另外在计算缩到某天所需的资源时,要判断这天是否比任务本身所需的时间还大,如果是就不必分配资源了(还没到要缩短天数的要求)
70分代码(线性枚举:超时):
双层嵌套循环O(n^2)会超时
#include <bits/stdc++.h>
using namespace std;
int t[100005];
int c[100005];
int main(){
int n,m,k;
int totalneed = 0;
cin >> n >> m >> k;
for(int i = 0;i < n;i++){
cin >> t[i] >> c[i];
totalneed += (t[i]-k)*c[i];
}
//资源足够缩到k天
if(m >= totalneed){
cout << k;
return 0;
}
int res = k;
//从小到大枚举天数,计算res天需要的资源,满足就结束
do{
res++;
totalneed = 0;
for(int i = 0;i < n;i++){
//判断是否需要分配资源缩短到res天
if(t[i] >= res){
totalneed += (t[i]-res)*c[i];
}
}
}while(m <= totalneed);
cout << res;
return 0;
}
100分代码(二分搜索/二分枚举):
由于本题的答案处于从k天起到第 max(t[i])
天,如下图:
具备线性单调的特点,采取二分枚举在 [k,max(t[i])]
内的天数,总会找到一个符合条件的解。
#include <bits/stdc++.h>
using namespace std;
int t[100005];
int c[100005];
int n,m,k;
bool canReduce(int target){
int totalneed = 0;
for(int i = 0;i < n;i++){
//判断是否需要分配资源缩短到res天
if(t[i] >= target){
totalneed += (t[i]-target)*c[i];
}
}
if(totalneed > m){//资源不够
return false;
}
return true;
}
int main(){
cin >> n >> m >> k;
int maxt = -1;
for(int i = 0;i < n;i++){
cin >> t[i] >> c[i];
if(maxt < t[i]){
maxt = t[i];
}
}
//二分搜索找左边界(最小值)
int left = k;
int right = maxt;
while(left <= right){
int mid = (left + right)/2;
//能缩短到mid天就继续尝试缩短
if(canReduce(mid)){
right = mid-1;
}
//不能缩短到mid天就放宽要求mid+1
else{
left = mid+1;
}
}
cout << left;
return 0;
}
需要特别注意while内的判断条件是否取等号!
不取等号会忽略掉区间内最后一个数的判断,也就是[left,left] (此时left == right),从而取不到left的值。
解法二:贪心
题目已经说明,总耗时取决于用时最长的区域,所以每次分配资源给最长的任务,使其时间缩短就可以达到目的。
90分代码(超时):
存在超时的问题,原因是每次计算缩短所需资源后又用了一个for将能缩短的任务缩短,实际上,sort之后最长的天数就是task.t[0]
,每次将与 task.t[0]
相同的任务缩短一天后,接着上一次的位置继续判断即可,不需要再用一个for将这些任务挨个减少1天,详见 下面的代码
#include <bits/stdc++.h>
using namespace std;
struct task{
int t;
int c;
};
task tasks[100005];
bool cmp(const task &a,const task &b){
return a.t > b.t;
}
int main(){
int n,m,k;
cin >> n >> m >> k;
for(int i = 0;i < n;i++){
cin >> tasks[i].t >> tasks[i].c;
}
sort(tasks,tasks+n,cmp);
while(tasks[0].t != k){
long long needc = 0;
int end = 0; //用于确定相同耗时的末尾索引
//计算最大耗时缩短一天需要的资源
for(int i = 0;i < n;i++){
//判断有几个最大(最大耗时可能有多个相同的)
if(tasks[0].t != tasks[i].t){
break;
}
else if(tasks[0].t == tasks[i].t){
needc += tasks[i].c;
if(i != 0){ //排除第一个
end++;
}
}
}
if(m >= needc){
m -= needc;
//这里不需要(将耗时相同的任务挨个减少一天)
for(int i = 0;i <= end;i++){
tasks[i].t--;
}
}else{//资源不足退出
break;
}
}
cout << tasks[0].t;
return 0;
}
100分代码:
实际上, task.t[0]
代表了最长的耗时(输出时也是输出task.t[0]
),所以每次计算所需资源后将比较时长的 起始位置 设置为 缩短后的第一个任务tasks[start].t
。
#include <bits/stdc++.h>
using namespace std;
struct task{
int t;
int c;
};
task tasks[100005];
bool cmp(const task &a,const task &b){
return a.t > b.t;
}
int main(){
int n,m,k;
cin >> n >> m >> k;
for(int i = 0;i < n;i++){
cin >> tasks[i].t >> tasks[i].c;
}
sort(tasks,tasks+n,cmp);
long long needc = 0;
int start = 0; //用于确定每次开始比较时的第一个任务
while(tasks[0].t != k){
//计算最长耗时的个数,并计算这些任务缩短一天所需的资源
if(tasks[0].t == tasks[start].t){
needc += tasks[start].c;
start++;
}
else if(tasks[0].t != tasks[start].t){
if(m >= needc){
//资源满足就缩短一天
m -= needc;
tasks[0].t--;
}else{//资源不足退出
break;
}
}
}
cout << tasks[0].t;
return 0;
}