一往直前!贪心法
贪心就是套方案或者数学证明呗,题做多了就有感觉了。
贪心法的证明大致有3种:
- 反证法
- 数学归纳(比如,先列举一个最简单的情况,如果有大小关系,我们可以交换两个项,把结果的大小进行比较)
- A>=B且A<=B则A=B.我们一般把A当做我们的贪心解法,B当做最优解,显然A<=B,我们只需要证明A>=B即可。
贪心中真的很多排序,堆,pair,结构体,要想清楚他们维护的是什么,考验的是基础的代码实现逻辑和双指针算法等。还有很多边界问题,比如前导0,相等,奇偶数…
书上的题:
区间调度问题:
这个我们都知道,选结束时间最早的嘛。
因为是对结束区间排序,我们把结束端点放进pair的first里,开始端点放在pair的second里。
pair<int,int> p[N];
void solve()
{
sort(p,p+n);
int t=p[0].first;
for(int i=1;i<n;i++)
{
if(p[i].second>t)
{
p[i].first=t;
ans++;
}
}
}
字典序最小问题
这题重要在对于一些特殊值,比如边界,相等的情况的处理。
这题的贪心策略是将S与S翻转过的字符串比较(不是真翻转,就是用指针倒着数即可),因为当头字符和尾字符相等,我们必须比较去掉他们后的下一个字符再做定论。
void solve()
{
int i=0,j=n-1;
while(i<=j)
{
bool flag=0;
for(int k=0;k<n;k++)
{
if(s[a+k]>s[b-k])
{
flag=0;
break;
}
if(a[a+k]<s[b-k])
{
flag=1;
break;
}
}
}
if(flag)
{
cout<<s[b];
b--;
}
else
{
cout<<s[a];
a++;
}
}
Saruman’s Army
这个策略也很简单,我们先找到能覆盖当前点的最小点,再找到这个点能覆盖到的最右边的点。如此循环。
void solve()
{
sort(a,a+n);
int i=0,ans=0;
while(i<n)
{
int s=a[i];
i++;
while(i<n&&a[i]<s+x)
i++;
i--;
int p=a[i];
while(i<n&&a[i]<=p+x)
i++;
ans++;
}
cout<<ans;
}
Fence Repair
这是一道经典的合并果子。。。
(合并果子等会会写到)
谈谈贪心的常用方式
贪心会与二分值的验证结合
洛谷的贪心题单(只做了提高组)
P2240部分背包问题
这根本不是个背包好吧,当然是先放单位重量价格最高的啦0v-
另外注意浮点数,不要直接比大小。
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
double w[N],v[N];
pair<double,int> a[N];
int main()
{
int n;
double t;
cin>>n>>t;
for(int i=0;i<n;i++)
{
cin>>w[i]>>v[i];
a[i].first=w[i]/v[i];
a[i].second=i;
}
double sum=0,res=0;
sort(a,a+n);
for(int i=0;i<n;i++)
{
if(w[a[i].second]-t<0.000001)
{
res+=v[a[i].second];
t-=w[a[i].second];
}
else
{
res+=t/w[a[i].second]*v[a[i].second];
break;
}
}
printf("%.2f",res);
return 0;
}
P1090合并果子
#include<bits/stdc++.h>
using namespace std;
const int N=10100;
int a[N];
priority_queue<int> q;
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i];
q.push(-a[i]);
}
int res=0,x1,x2;
while(q.size()!=1)
{
x1=q.top();
q.pop();
x2=q.top();
q.pop();
res=res-x1-x2;
q.push(x1+x2);
}
cout<<res;
return 0;
}
P1106删数问题
这道题想出贪心策略后还要考虑两点:
- 如果全删了,要输出0。
- 边界:如果最后遍历到i=n-k了,后面的直接全删掉。
#include<bits/stdc++.h>
using namespace std;
int a[500];
int main()
{
string m;
cin>>m;
int k;
cin>>k;
int t=1;
int flag=0,cnt=0;
for(int i=1;i<=m.length();i++)
{
a[i]=m[i-1]-'0';
}
int rest=m.length()-k;
while(cnt<rest)
{
int v=t;
for(int i=t;i<=t+k;i++)
{
int x=a[v];
if(a[i]<x)
{
v=i;
}
}
if(a[v])
{
flag=1;
}
if(flag)
{
cout<<a[v];
}
k=k-(v-t);
t=v+1;
cnt++;
}
if(!flag)
cout<<0;
return 0;
}
P4995跳跳!
我是把他想成了一根直线,从一端跳到另一端可以用双指针。
#include<bits/stdc++.h>
using namespace std;
const int N=310;
long long h[310];
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>h[i];
}
sort(h,h+n);
long long res=0;
if(h[n-1]>(-h[0])||h[0]>=0)
{
res+=h[n-1]*h[n-1];
reverse(h,h+n);
}
else
{
res+=h[0]*h[0];
}
int i=0,j=n-1;
if(n%2==0)
{
while(i-j!=1)
{
res=res+(h[i]-h[j])*(h[i]-h[j]);
i++;
res=res+(h[i]-h[j])*(h[i]-h[j]);
j--;
}
}
else
{
while(i!=j)
{
res=res+(h[i]-h[j])*(h[i]-h[j]);
i++;
res=res+(h[i]-h[j])*(h[i]-h[j]);
j--;
}
}
if(i==j-1)
{
res+=(h[i]-h[j])*(h[i]-h[j]);
}
cout<<res;
return 0;
}
P4447 分组
可以暴力枚举每个数加在哪个后面,但是是O(n2)的
用堆维护最小值可以到O(nlogn)
对于每个序列,如果接不上了就可以从堆中丢弃,并取得长度最小值,如果接的上就接上去。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
priority_queue<pair<int,int> > pq;
int minn=INT_MAX;
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
sort(a,a+n);
for(int i=0;i<n;i++)
{
if(pq.empty())
{
pq.push({-a[i],-1});
continue;
}
while(!pq.empty())
{
if(a[i]>1-pq.top().first)
{
//cout<<a[i]<<" "<<1-pq.top().first<<pq.top().second<<endl;
//cout<<pq.size()<<endl;
minn=min(minn,-pq.top().second);
pq.pop();
}
else
break;
}
if(pq.empty()||pq.top().first==-a[i])
{
pq.push({-a[i],-1});
continue;
}
//只剩下一种a[i]=-pq.top+1
pair<int,int> x=pq.top();
pq.pop();
x.second--;
x.first--;
pq.push(x);
}
while(!pq.empty())
{
minn=min(minn,-pq.top().second);
pq.pop();
}
cout<<minn;
return 0;
}
P1080 国王游戏
涉及难点:贪心性质的证明和高精度乘除法
对于贪心性质的证明:我们先看一题这道题的加减法版本
耍杂技的牛
这题是对w+s排序,也不需要用到高精度
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
pair<int,int> a[N];
int w;
int s;
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>w>>s;
a[i].first=w+s;
a[i].second=s;
}
sort(a,a+n);
int sum=0;
int res=-100000000;
for(int i=0;i<n;i++)
{
sum-=a[i].second;
res=max(res,sum);
sum+=a[i].first;
}
cout<<res;
return 0;
}
思路:见耍杂技的牛
国王游戏
简单的说就是交换两项,看看他们的最大值有没有变化。当然这个证明基于你已经有贪心思路的情况下。
对于高精度套板就行
乘法:令t=a[i]*b,把t%10的结果留下来,把t/10的结果进位
除法:与列竖式一样的,不断借位
最后如果有多余的进位也放进vector里,把t/b留下来,t%b继续借位除
注意把结果反过来pop前导零,或者做一个标记标记前导零
另外,我们直接用的反向的数字vector进行操作,要注意各个模板的变化。
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int a[N];
int b[N];
pair<int,int> p[N];
vector<int> mul(vector<int> a,int b)
{
vector<int> v;
int t=0;
for(int i=0;i<a.size();i++)
{
t+=a[i]*b;
v.push_back(t%10);
t/=10;
}
while(t)
{
v.push_back(t%10);
t/=10;
}
return v;
}
/*vector<int> div(vector<int>a, int b)
{
vector<int> c;
bool is_first = true;
for (int i = a.size() - 1, t = 0; i >= 0; i -- )
{
t = t * 10 + a[i];
int x = t / b;
if (!is_first || x)
{
is_first = false;
c.push_back(x);
}
t %= b;
}
reverse(c.begin(), c.end());
return c;
}*/
vector<int> div(vector<int>a,int b)
{
vector<int> v;
int r=0;
for(int i=a.size()-1;i>=0;i--)
{
r=r*10+a[i];
v.push_back(r/b);
r=r%b;
}
reverse(v.begin(),v.end());
while(v.size()>1&&v.back()==0)
{
v.pop_back();
}
return v;
}
vector<int> maxx(vector<int> a,vector<int> b)
{
if(a.size()>b.size()) return a;
if(a.size()<b.size()) return b;
if(vector<int>(a.rbegin(),a.rend())>vector<int>(b.rbegin(),b.rend())) return a;
return b;
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n+1;i++)
{
cin>>a[i]>>b[i];
p[i].first=a[i]*b[i];
p[i].second=a[i];
}
sort(p+1,p+1+n);
vector<int> product(1,1);
vector<int> res(1,0);
for(int i=0;i<=n;i++)
{
if(i) res=maxx(res,div(product,p[i].first/p[i].second));
product=mul(product,p[i].second);
}
reverse(res.begin(),res.end());
for(int i=0;i<res.size();i++)
{
cout<<res[i];
}
return 0;
}
Acwing
区间问题
区间选点
对于区间的贪心,不妨试一下,左端排序,右端排序,双端排序等等
最大不相交区间数量
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
pair<int,int> p;
vector<pair<int,int> > v;
int main()
{
int n;
cin>>n;
int k=n;
while(n--)
{
int l,r;
cin>>l>>r;
p.second=l;
p.first=r;
v.push_back(p);
}
sort(v.begin(),v.end());
int tot=1;
int pos=v[0].first;
for(int i=1;i<k;i++)
{
if(v[i].second>pos)
{
tot++;
pos=v[i].first;
}
}
cout<<tot;
return 0;
}
区间分组
#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
pair<int,int> p;
vector<pair<int,int> > v;
priority_queue<int,vector<int>,greater<int>> q;
int main()
{
int n;
cin>>n;
int k=n;
while(n--)
{
int l,r;
cin>>l>>r;
p.first=l;
p.second=r;
v.push_back(p);
}
sort(v.begin(),v.end());
int pos=v[0].first;
for(int i=0;i<k;i++)
{
if(q.empty()||q.top()>=v[i].first)
{
q.push(v[i].second);
}
else
{
q.push(max(q.top(),v[i].second));
q.pop();
}
}
cout<<q.size();
return 0;
}
区间覆盖
#include <iostream>
#include <algorithm>
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;
scanf("%d%d", &st, &ed);
scanf("%d", &n);
for (int i = 0; i < n; i ++ )
{
int l, r;
scanf("%d%d", &l, &r);
range[i] = {l, r};
}
sort(range, range + n);
int res = 0;
bool success = 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)
{
success = true;
break;
}
st = r;
i = j - 1;
}
if (!success) res = -1;
printf("%d\n", res);
return 0;
}
合并果子
#include<iostream>
#include<queue>
using namespace std;
const int N=2e5+10;
priority_queue<int,vector<int>,greater<int>> p;
int main()
{
int n;
cin>>n;
while(n--)
{
int x;
cin>>x;
p.push(x);
}
int res=0;
while(p.size()>1)
{
int a=p.top();
p.pop();
int b=p.top();
p.pop();
p.push(a+b);
res+=a+b;
}
cout<<res;
return 0;
}
货舱选址
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
int q[N];
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);
sort(q, q + n);
int res = 0;
for (int i = 0; i < n; i ++ ) res += abs(q[i] - q[n / 2]);
printf("%d\n", res);
return 0;
}
耍杂技的牛
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
pair<int,int> a[N];
int w;
int s;
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>w>>s;
a[i].first=w+s;
a[i].second=s;
}
sort(a,a+n);
int sum=0;
int res=-100000000;
for(int i=0;i<n;i++)
{
sum-=a[i].second;
res=max(res,sum);
sum+=a[i].first;
}
cout<<res;
return 0;
算法竞赛进阶指南(还没做,之后更)
POJ 3614 SunScreen