贪心:求最优性问题方法
Question 01 [区间选点]
有 n 个区间,每个区间至少有一个点
求至少需要几个点
Answer 01
右端点升序排序
若左端点已被覆盖便略过
否则选当前区间右端点
Code
同Q2 Code
exCode ACP2033 [种树]
树可能有多棵,均放在右侧把后面布满即可
注意右侧摆放的位置可能有tree
需要进行特判
#include<bits/stdc++.h>
using namespace std;
const int N=88500;
struct range{int l,r,key;}k[N];
int n,tmp;
bool tree[N];
bool cmp(range A,range B){return A.r<B.r;}
int tree_num(int id,int rim){
int tot=0;
for(int i=k[id].l;i<=rim;i++)if(tree[i])++tot;
return tot;
}
int main(){
scanf("%d%d",&tmp,&n);
for(int i=1;i<=n;i++)scanf("%d%d%d",&k[i].l,&k[i].r,&k[i].key);
sort(k+1,k+n+1,cmp);
int ans=0,rim=-1999;
for(int i=1;i<=n;i++){
int w=k[i].key-tree_num(i,rim),it=k[i].r;
while(w>0){
if(tree[it]){--it;continue;}
tree[it]=true;
--w,++ans;
}
rim=k[i].r;
}
printf("%d",ans);
return 0;
}
Question 02 [区间选择]
有n个区间,选取尽量多个区间使所取区间两两不相交。
Answer 02
右端点升序排序
若左端点已被覆盖便略过
否则更新右端点限制
实际上和 Question 01 是一样的
Code
#include<bits/stdc++.h>
using namespace std;
struct range{int l,r;}k[1024];
int n;
bool cmp(range A,range B){return A.r<B.r;}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d%d",&k[i].l,&k[i].r);
sort(k+1,k+n+1,cmp);
int t=-1918,ans=0;
for(int i=1;i<=n;i++){
if(t>=k[i].l)continue;
++ans,t=k[i].r;
}
printf("%d",ans);
return 0;
}
exCode ACP2032 [活动安排]
注意左闭右开
#include<bits/stdc++.h>
using namespace std;
struct range{int l,r;}k[1024];
int n;
bool cmp(range A,range B){return A.r<B.r;}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d%d",&k[i].l,&k[i].r);
sort(k+1,k+n+1,cmp);
int t=-1918,ans=0;
for(int i=1;i<=n;i++){
if(t>=k[i].l)continue;
++ans,t=k[i].r;
}
printf("%d",ans);
return 0;
}
Question 03 [区间覆盖]
选择最少区间以覆盖大区间
Answer
左端点排序
选择能覆盖当前大区间左端点且右端点最靠后的区间
更新左端点
直到没有区间能覆盖当前大区间左端点或区间覆盖完成为止
Code
#include<bits/stdc++.h>
using namespace std;
const int N=100098;
struct range{int l,r;}k[N];
int n,lim,rim;
bool cmp(range a,range b){
return a.l<b.l;
}
int main(){
scanf("%d%d%d",&lim,&rim,&n);
for(int i=1;i<=n;i++)scanf("%d%d",&k[i].l,&k[i].r);
sort(k+1,k+n+1,cmp);
int best,bid,tmp=0,ans=0;
while(lim<rim){
bid=0,best=lim;
for(int i=tmp+1;i<=n;i++){
if(k[i].l>lim)break;
if(k[i].r>best)bid=i,best=k[i].r;
}
if(bid==0){puts("-1"),exit(0);}
lim=best;
tmp=bid;
++ans;
}
printf("%d",ans);
return 0;
}
exCode ACP2034 [喷水装置]
易见只有中间的长方形区域才是有效覆盖
其余部分会有遗漏
所以半径小于等于宽度一半的可以直接舍掉
(半径等于宽度一半有效覆盖只有一条线)
勾股求出有效范围后直接套板子
#include<bits/stdc++.h>
using namespace std;
const int N=15009;
struct range{double l,r;}k[N];
bool cmp(range A,range B){return A.l<B.l;}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int T,n_pre,n,ans,best_id,last_qid;
double W,lim,rim,pos,r,best_key;
cin>>T;
while(T--){
lim=0.0,n=0,ans=0,last_qid=0;
cin>>n_pre>>rim>>W;
for(int i=1;i<=n_pre;i++){
cin>>pos>>r;
if(r*2.0-W<1e-6)continue;
k[++n]={pos-sqrt(r*r-W*W*0.25),pos+sqrt(r*r-W*W*0.25)};
}
sort(k+1,k+n+1,cmp);
while(rim>lim){
best_id=-1,best_key=lim;
for(int i=last_qid+1;i<=n;i++){
if(k[i].l>lim)break;
if(k[i].r>best_key)best_key=k[i].r,best_id=i;
}
if(best_id==-1){cout<<"-1\n";goto fuck;}
last_qid=best_id;
++ans;
lim=best_key;
}
cout<<ans<<"\n";
fuck:;
}
return 0;
}
Question 04 [区间分组]
有n个区间,分成尽量少组使得每组中区间两两不相交
Answer
左端点正序排序
如果暂无分组或当前左端点小于所有组右端点最小值则新开一组
否则由于左端点正序
插入任意组均可,便插入最小值组
每组仅记录右端点
用堆取最小值即可
Code
#include<bits/stdc++.h>
using namespace std;
const int N=100067;
int n;
priority_queue<int,vector<int>,greater<int> > q;
struct range{int l,r;}k[N];
bool cmp(range a,range b){return a.l<b.l;}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d%d",&k[i].l,&k[i].r);
sort(k+1,k+n+1,cmp);
for(int i=1;i<=n;i++){
if(q.empty()||k[i].l<=q.top())q.push(k[i].r);
else q.pop(),q.push(k[i].r);
}
printf("%d",q.size());
return 0;
}
Question 05 [ACP2035 双路加工]
有 n 个任务,每个任务都需要被两个机器完成分别需要 A[i],B[i]时刻
必须先用 A 机器完成再用 B 机器完成
求所需最低时间
Answer
本题答案抽象
方法如下
if x First and y Second
then their must be
x.A+max(x.B,y.A)+y.B<y.A+max(x.A,y.B)+x.B
define tot x.A+x.B+y.A+y.B
tot-min(x.B,y.A)<tot-min(x.A,y.B)
min(x.A,y.B)<min(x.B,y.A)
只要构造一个满足该要求的序列即可
为了严谨我们将等式挂等
min(x.A,y.B)<=min(x.B,y.A)
经过一位名叫 Johnson 的故人的论证
(1)令N1={1<=i<=n|ai<bi},N2={1<=i<=n|ai>=bi}
(2)将N1中作业按 a 的非降排序;将N2中作业按 b 的非增排序
(3)N1中作业的尾部接上N2中作业就构成了满足 Johnson 法则的最优调度
然后模拟加工
Time(B机器完成第 i 个任务)=Time(第 i 个任务花费B)+max(Time(B机器完成第 i-1 个任务),Time(A 机器完成第 i 个任务))
Time(B机器完成第 i-1 个任务)------------->B 已经完成
Time(A机器完成第 i 个任务)--------------->任务可以加工
Code
#include<bits/stdc++.h>
using namespace std;
int n,order[1045];
struct task{int A=0,B=0;}k[1045];
bool cmp(task a,task b){return min(a.A,a.B)<min(b.A,b.B);}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&k[i].A);
for(int i=1;i<=n;i++)scanf("%d",&k[i].B);
sort(k+1,k+n+1,cmp);
int ff=0,tt=n+1;
for(int i=1;i<=n;i++){
if(k[i].A<k[i].B){
order[++ff]=i;
}else{
order[--tt]=i;
}
}
int BB=0,timer=0;
for(int i=1;i<=n;i++){
timer+=k[order[i]].A;
BB=max(BB,timer)+k[order[i]].B;
}
printf("%d",BB);
return 0;
}
Question 06 [ACP2042 糖果传递]
有 n 个小朋友坐成一圈
每人有一定糖果
每人只能给左右两人传递糖果
求使所有人糖果数相同的最小传递次数
Answer
推式子题
设i给i-1个数为give[i]个(give[1]为 1 给 n 的个数)
所求为|give[1]|+|give[2]|+...+|give[n]|最小值
具体推导过程见Code
最终这一坨化为
|pos[1]-c[1]|+|pos[1]-c[2]|+...+|pos[1]-c[n]|
其中 c 数组为已得
根据初中绝对值知识
pos[1]取{ c[n/2+1] (n%2==1) }时原式最小 (c 数组有序)
{ c[n/2] to c[n/2+1] (n%2==0) }
所以统计答案即可
Code
#include<bits/stdc++.h>
using namespace std;
const int N=1000908;
int n;
long long lable[N],average,c[N];
long long llabs(long long ll){
if(ll>0)return ll; return ll*-1ll;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lld",&lable[i]),average+=lable[i];
average/=(long long)n;
c[n]=average-lable[n];
for(int i=n-1;i>=1;i--)c[i]=c[i+1]+average-lable[i];
sort(c+1,c+n+1);
long long give01=c[(n+1)/2],ans=0;
for(int i=1;i<=n;i++)ans+=llabs(c[i]-give01);
printf("%lld",ans);
return 0;
}
Question 07 [ACP2041 钓鱼]
www.accoders.com/problem.php?cid=5014&pid=5
Answer
考虑枚举只经过前几个池塘
显然一定不走回头路最优
接下来就是贪心
我们想要在当前的5min中在目前鱼量最大的池塘钓鱼
用堆维护,每次弹堆顶并更新
注意时间有可能走不到最后的池塘
特判即可
Code
#include<bits/stdc++.h>
using namespace std;
const int N=9999;
int n,leisure,fish[N],delta[N],trip[N],ans,answer=-1;
struct node{
int id,key;
bool operator <(const node &rhs)const{
return key<rhs.key;
}
};
int main(){
scanf("%d%d",&n,&leisure);
leisure*=12;
node t;
for(int i=1;i<=n;i++)scanf("%d",&fish[i]);
for(int i=1;i<=n;i++)scanf("%d",&delta[i]);
for(int i=1;i<n;i++)scanf("%d",&trip[i]),trip[i]+=trip[i-1];
for(int i=1,left_time;i<=n;i++){
ans=0;
priority_queue<node> q;
left_time=leisure-trip[i-1];
if(left_time<=0)break;
for(int j=1;j<=i;j++)q.push({j,fish[j]});
while(left_time--){
t=q.top(),q.pop();
if(t.key<0)break;
ans+=t.key;
q.push({t.id,t.key-delta[t.id]});
}
answer=max(answer,ans);
}
printf("%d",answer);
return 0;
}
Question 08 [ACP2036 智力大波浪]
n 个时段,每个时段完成一个任务,计时从1 开始
每个任务都有截止日期和无法完成的代价
求最小代价
Answer
将任务代价从大到小排序
尽量后完成
完成不了就记录代价
Code
#include<bits/stdc++.h>
using namespace std;
const int N=900;
int money,n;
bool st[1008];
struct task{int deadline,beat;}k[N];
bool cmp(task a,task b){return a.beat>b.beat;}
int main(){
int die=-1,it;
scanf("%d%d",&money,&n);
for(int i=1;i<=n;i++)scanf("%d",&k[i].deadline);
for(int i=1;i<=n;i++)scanf("%d",&k[i].beat);
sort(k+1,k+n+1,cmp);
for(int i=1;i<=n;i++){
it=k[i].deadline;
if(it<=die){money-=k[i].beat;continue;}
while(it>0&&st[it])--it;
if(it==0){money-=k[i].beat,die=k[i].deadline;continue;}
st[it]=true;
}
printf("%d",money);
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek-R1本地部署如何选择适合你的版本?看这里
· 开源的 DeepSeek-R1「GitHub 热点速览」
· 传国玉玺易主,ai.com竟然跳转到国产AI
· 揭秘 Sdcb Chats 如何解析 DeepSeek-R1 思维链
· 自己如何在本地电脑从零搭建DeepSeek!手把手教学,快来看看! (建议收藏)