每次选择当前的最优解
没什么固定的套路,先试一些做法,举例子验证,尝试证明一下
严格地推
区间问题
例1:
1.将每个区间按照右端点从小到大排序
2.从前往后依次枚举每个区间(如果当前区间已经包含点,则直接pass,否则选择当前区间的右端点)
证明:做法是每个区间至少放一个点,方案肯定是可行的
用cnt表示当前选择的可行的方案的点数
ans表示所有可行方案的点数的最小值,即最优解
所以ans<=cnt
先在第一个区间最右端放一个点,如果含于其它区间,那么就不用管,下一个点的放置在下一个不包含第一个点的区间的最右端,同理,这样,要想覆盖所有的区间,所有可行方案的点数(包括ans)至少要大于等于cnt,即ans>=cnt
所以ans=cnt,即最优解为cnt
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100010;
int n;
struct Range{
int l,r;
bool operator<(const Range &W)const{
return r<W.r;
}
}range[N];
int main()
{
cin>>n;
for(int i=0;i<n;i++){
int l,r;
cin>>l>>r;
range[i]={l,r};
}
sort(range,range+n);
int res=0,ed=-2e9;
for(int i=0;i<n;i++){
if(range[i].l>ed){
res++;
ed=range[i].r;
}
}
cout<<res<<endl;
return 0;
}
例2:
![]()
1.将每个区间按照右端点从小到大排序
2.从前往后依次枚举每个区间(如果当前区间已经包含点,则直接pass,否则选择当前区间的右端点)
ans>=cnt
假设ans>cnt,那么可以找到比cnt更多的不相交的区间,但实际上cnt个点就可以覆盖所有区间,不可能再多找出一个区间了,矛盾
所以cnt=ans即最优解
代码与例1完全一样
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100010;
int n;
struct Range{
int l,r;
bool operator<(const Range &W)const{
return r<W.r;
}
}range[N];
int main()
{
cin>>n;
for(int i=0;i<n;i++){
int l,r;
cin>>l>>r;
range[i]={l,r};
}
sort(range,range+n);
int res=0,ed=-2e9;
for(int i=0;i<n;i++){
if(range[i].l>ed){
res++;
ed=range[i].r;
}
}
cout<<res<<endl;
return 0;
}
例3:
1.将区间左端点从小到大排序
2.从前往后处理每个区间
判断能否将其放到某个现有的组中(即当前区间左端点是不是大于组内最右端点L[i]>Max_r)
如果不存在这样的组,则开新组,然后再将其放进去
如果存在这样的组,将其放进去,并更新当前的组最右端点Max_r
优先队列里放的是每组的最右端点坐标,小根堆队列的队头是最小那一组的最右端点坐标,如果队列为空或者最小那一组最右端点坐标大于等于将要放置的那段区间左端点坐标时(第一组都放不了,后面的组更放不了,说明放入不了已有组内),那么新开一个组
否则可以放进去第一个组内,那么将队头(第一组最右端坐标)删掉,并且重新更新为放入的区间最右端坐标
最后输出队列里元素个数,即组数
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=100010;
int n;
struct Range{
int l,r;
bool operator<(const Range &W)const{
return l<W.l;
}
}range[N];
int main()
{
cin>>n;
for(int i=0;i<n;i++){
int l,r;
cin>>l>>r;
range[i]={l,r};
}
sort(range,range+n);
priority_queue<int,vector<int>,greater<int>>heap;
for(int i=0;i<n;i++){
auto t=range[i];
if(heap.empty()||heap.top()>=t.l) heap.push(t.r);
else{
heap.pop();
heap.push(t.r);
}
cout<<heap.size()<<endl;
return 0;
}
例4:区间覆盖
1.将所有区间按照左端点从小到大排序
2.从前往后依次枚举每个区间,在所有能覆盖到start的区间中,选择右端点最大的区间,然后将start更新成右端点的最大值
利用双指针的算法,i从0到n遍历,最终i记录j找到哪一个了i=j-1,然后j从这个开始找在能覆盖st的情况下,j往后找,找到右端点最大的区间(j的作用),其中r记录那个右端点坐标,最后把j-1赋值给i,然后下一次j从这开始往后找
记录最后能覆盖到start的区间最大右端点坐标,最后把r赋给start,res记录用的区间数量
如果r小于start,说明覆盖不了start了,即中间有空隙,那么就将res记录为-1,退出循环,输出-1,如果将end覆盖了,那么后面也没必要做了,退出循环,最后可能中间没有空隙,但是最后没有覆盖到end,所以也要判断一下
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100010;
int n;
struct Range{
int l,r;
bool operator<(const Range &W)const{
return l<W.l;
}
}range[N];
int main()
{
int st,ed;
cin>>st>>ed;
cin>>n;
for(int i=0;i<n;i++){
int l,r;
cin>>l>>r;
range[i]={l,r};
}
sort(range,range+n);
int res=0;
bool flag=false;
for(int i=0;i<n;i++){
int j=i,r=-2e9;
while(j<n&&range[j].l<=st){
r=max(r,range[j].r);
j++;
}
if(r<st){
res=-1;
break;
}
res++;
if(r>=ed){
flag=true;
break;
}
st=r;
i=j-1;
}
if(!flag) res=-1;
cout<<res<<endl;
return 0;
}
例5:合并果子
每次都选值最小的两个点合并
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
int main()
{
int n;
cin>>n;
priority_queue<int,vector<int>,greater<int>>heap;
while(n--){
int x;
cin>>x;
heap.push(x);
}
int res=0;
while(heap.size()>1){
int a=heap.top();
heap.pop();
int b=heap.top();
heap.pop();
res+=a+b;
heap.push(a+b);
}
cout<<res<<endl;
return 0;
}
例6:
t1 t2 t3 t4...tn 总时间=t1*(n-1)+t2*(n-2)+t3*(n-3)+...tn-1*1+tn*0
按照从小到大的顺序排序,总时间最小
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int N=100010;
int n;
int t[N];
int main()
{
cin>>n;
for(int i=0;i<n;i++) cin>>t[i];
sort(t,t+n);
LL res=0;
for(int i=0;i<n;i++) res+=t[i]*(n-i-1);
cout<<res<<endl;
return 0;
}
例7:货舱选址
分组,x1与xn分为一组,x2与xn-1分为一组...以此类推
求x到两个点距离之和,要使距离之和最小,则x在两点之间(包括端点)
f(x)=|x1-x|+|x2-x|+...|xn-x|>=xn-x1+xn-1-x2+...
如果n为偶数,那么x取在最中间的两个数之间,如果n为奇数,那么x取在最中间的那个数的位置
只要枚举所有点,求每个点到中间数的点的距离之和
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100010;
int n;
int a[N];
int main()
{
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
sort(a,a+n);
int res=0;
for(int i=0;i<n;i++) res+=abs(a[i]-a[n/2]);
cout<<res<<endl;
return 0;
}
例8:
按照wi+si从小到大的顺序排,将小的往上叠
证明:假设不是从小到大排,那么必定存在前一个总和大于后一个总和
即wi+si>w(i+1)+s(i+1)
与交换后,去掉相同部分
为变好看一些,方便观察比较,每个数加上si+s(i+1)
发现交换后的数值si<wi+si,w(i+1)+s(i+1),所以交换后第i个位置和第i+1个位置两者的危险系数的最大值会变小,所以只要有交换就会变小,那么就把所有都换过来
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef pair<int,int>PII;
const int N=50010;
int n;
PII cow[N];
int main()
{
cin>>n;
for(int i=0;i<n;i++){
int w,s;
cin>>w>>s;
cow[i]={w+s,w};
}
sort(cow,cow+n);
int res=-2e9,sum=0;
for(int i=0;i<n;i++){
int w=cow[i].second,s=cow[i].first-w;
res=max(res,sum-s);
sum+=w;
}
cout<<res<<endl;
return 0;
}