title: 专题1 - 贪心、二分
tags: [贪心, 二分]
categories: [OI, 算法专题]
二分
二分是很基础的知识了,在这里我还是不完全按照题目的顺序,一点一点来梳理一下二分。
二分,根本含义是利用单调性,寻找取值变化的改变点。一般来说,我们可以把可以使用二分解决的问题建模为0/1序列,这个序列具有单调性。通过二分过程中代表的区间[left,right]的不断缩小,来接近这个点。
为什么要用二分?因为二分可以在几乎没有预处理的情况下,单靠check函数,来最快的逼近答案。
整数二分模板
尽可能取大值:
int left = min,right = max,ans = defaultValue;
while (left <= right){
int mid = left + (right - left) / 2;
if (check(mid)){
left = mid + 1;
ans = mid;
}
else{
right = mid - 1;
}
}
尽可能取小值:
int left = min,right = max,ans = defaultValue;
while (left <= right){
int mid = left + (right - left) / 2;
if (check(mid)){
right = mid - 1;
ans = mid;
}
else{
left = mid + 1;
}
}
check(ans)为true。也就是说,其得到的结果是闭区间。
这个模板是我最适应的。一开始我对边界的划分很迷,这样写感觉很舒服,对边界的划分比较无脑。
实数二分模板
尽可能取大值:
double left = min,right = max,ans = defaultValue;
while ((right - left) >= eps){
double mid = (left + right) /2 ;
if (check(mid)){
left = mid;
ans = mid;
}
else{
right = mid;
}
}
尽可能取小值:
double left = min,right = max,ans = defaultValue;
while ((right - left) >= eps){
double mid = (left + right) /2 ;
if (check(mid)){
right = mid;
ans = mid;
}
else{
left = mid;
}
}
诶,发现大同小异。
贪心
贪心的思考从下面几个方面考虑:
1.在工时固定的情况下,使得更多的时间做工
2.利用单调性,不走无用功
3.分解答案,将复杂构成的答案变得简单
习题
P13932 蓝桥杯 2022 省 Java B 求阶乘
原题 · 标签:#二分 #数学
考点
发现性质后简单二分
题意
满足 \(N!\) 的末尾恰好有 \(K\) 个 \(0\) 的最小的 \(N\) 是多少?
如果这样的 \(N\) 不存在输出 \(-1\)。
思路
发现如果把\(N!\)质因数分解,则\(5\)的个数一定小于\(2\)的个数。也就是说,末尾\(0\)的数量取决于\(5\)的个数。
那么就二分,得到最小的\(N\)使得其阶乘的\(5\)的因子个数大于\(K\)。
代码
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define int long long
#define pii pair<int,int>
#define DEBUG 1
#if DEBUG
#define d(x) cout << #x << " = " << x << endl;
#define da(a,l,r) cout << #a << "[" << l << "-" << r << "]:"; f(i,l,r) cout << a[i] << " "; cout << endl;
#else
#define d(x)
#define da(a,l,r)
#endif
int k;
int check(int x){
int res = 0;
while (x){
x /= 5;
res += x;
}
return res;
}
signed main() {
cin >> k;
int l = 0,r=5e18,ans = -1;
while (l <= r){
int mid = (l + r) / 2;
if (check(mid) >= k){
ans = mid;
r = mid - 1;
}
else{
l = mid + 1;
}
}
if (check(ans) == k) cout << ans;
else cout << -1;
return 0;
}
P4105 HEOI2014 南园满地堆轻絮
原题 · 标签:#二分 #贪心
考点
最小值最大经典习题+贪心check
题意
- 按给定递推公式生成长度为 \(n\) 的整数序列 \(A\)。
- 构造非递减序列 \(B\),要求最小化 \(\max|A_i-B_i|\),输出这个最小的最大值。
思路
最小值最大,就二分。
由于要求非递减,则在要求的限制下越小越有利于之后的发展。
所以顺着来,如果本来就可以,就取减去最小值与本来的数的max;如果不可以,不能修改挽回就返回false,否则原数加上最小值与要求取min。
代码
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define int long long
#define pii pair<int,int>
#define DEBUG 1
#if DEBUG
#define d(x) cout << #x << " = " << x << endl;
#define da(a,l,r) cout << #a << "[" << l << "-" << r << "]:"; f(i,l,r) cout << a[i] << " "; cout << endl;
#else
#define d(x)
#define da(a,l,r)
#endif
const int N = 5e6+10;
int n,sa,sb,sc,sd,a1,mod;
int a[N],b[N];
int fac(int x){
return ((sa * x%mod * x )%mod * x%mod + sb * x %mod* x%mod + sc * x%mod + sd) % mod;
}
void init(){
a[1] = a1;
f(i,2,n){
a[i] = (fac(a[i-1]) %mod+ fac(a[i-2])%mod) % mod;
}
}
bool check(int x){
memset(b,0,sizeof b);
f(i,1,n){
// 本来就行
if (a[i] >= b[i-1]){
b[i] = max(b[i-1],a[i]-x);
}
// 得改
else{
// 改不了
if (abs(b[i-1] - a[i]) > x){
return false;
}
// 能改
b[i] = min(b[i-1],a[i] + x);
}
}
return true;
}
signed main() {
cin >> n >> sa >> sb >> sc >> sd >> a1 >> mod;
init();
// da(a,1,n);
int l = 0,r = 2e9,ans;
while (l <= r){
int mid = (l + r) / 2;
// d(mid);
if (check(mid)){
// d(mid);
r = mid - 1;
ans = mid;
}
else{
l = mid + 1;
}
}
cout << ans;
return 0;
}
P6093 JSOI2015 套娃
原题 · 标签:#贪心 #std
考点
贪心+std
思路
TIP1
答案可以分解为不套娃的答案 \(-\) 套娃产生的贡献。
TIP2
不套娃的答案是 \(\sum_{i=1}^{n} In_i \times B_i\)。
若把 \(i\) 号套娃放在 \(j\) 号套娃里面,则会使答案减少 \(out_i \times b_j\)。
TIP3
从大套娃的角度考虑。如果有多个大套娃,有资格竞争同个小套娃,那就是 \(b\) 大者得。
从小套娃的角度考虑,如果有多个小套娃,有资格竞争同个大套娃,那就是 \(out\) 大者得。
你说的我都懂,咋实现啊?
multiset
我咋以前没听说过
思路历程
发现好像并不是娃都能套上,甚至套上的娃比较少,那能不能从套娃这个行为对答案的贡献出发?
想了想,不套娃的贡献很好算,套娃就是上述分开想一想,然后贪心。
然后就发现复杂度应该能降,但我不会了看题解发现了
multiset这个东西,set 这个东西我几乎不用。对于要处理数组处理不到的东西就用 map 或者 unordered_map,当加强版数组用。现在发现 std 还真是个好东西。
代码
代码
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define int long long
#define pii pair<int,int>
#define DEBUG 1
#if DEBUG
#define d(x) cout << #x << " = " << x << endl;
#define da(a,l,r) cout << #a << "[" << l << "-" << r << "]:"; f(i,l,r) cout << a[i] << " "; cout << endl;
#else
#define d(x)
#define da(a,l,r)
#endif
const int N = 2e5+10;
struct st{
int out,in,b;
};
bool cmp(st a,st b){
return a.b > b.b;
}
st a[N];
multiset <int> s;
int n;
int ans;
signed main() {
cin >> n;
f(i,1,n){
cin >> a[i].out >> a[i].in >> a[i].b;
ans += a[i].in * a[i].b;
s.insert(a[i].out);
}
sort(a+1,a+1+n,cmp);
f(i,1,n){
auto it = s.lower_bound(a[i].in);
if (it != s.begin()){
ans -= (*--it)*a[i].b;
s.erase(it);
}
}
cout << ans;
return 0;
}
P14333 JOI2021 预选赛 R2 安全检查 Safety Inspection
原题 · 标签:#贪心
考点
抽离二分+贪心check
题意
数轴上有 \(N\) 个按坐标升序排列的设施,每个设施需完成 \(B_i\) 项检查。共 \(K\) 名工人,初始位于坐标 \(0\),每人每分钟可移动1单位距离或完成1项检查。
求完成所有检查的最短时间。
数据范围
- \(1 \le N \le 10^5\)
- \(1 \le K,A_i,B_i \le 10^9\)
- 保证 \(A_1<A_2<\dots<A_N\),所有输入均为整数。
思路
发现可以从路途最远的项目上开始考虑。
可以将题目理解为使工人工作时间的最大值最小,所以二分时间。
考虑在路途中的时间没有做工,也就是说从路途大的工作项目开始考虑,工作的人数应尽可能小,才可以使在路途上的时间最少。但是又不能使在二分的时间内做不完。
这样也可能会浪费工时,因为可能做完后工人就歇着了。所以取模,剩下的工时给下一大的项目用。
代码
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define int long long
#define pii pair<int,int>
#define DEBUG 1
#if DEBUG
#define d(x) cout << #x << " = " << x << endl;
#define da(a,l,r) cout << #a << "[" << l << "-" << r << "]:"; f(i,l,r) cout << a[i] << " "; cout << endl;
#else
#define d(x)
#define da(a,l,r)
#endif
const int N = 2e5+10;
int n,k;
int a[N],b[N];
bool check(int mid){
int cnt = 0,ans = 0;
for (int i = n;i >= 1;i--){
if (cnt >= b[i]){
cnt -= b[i];
}
else{
int x = (b[i] - cnt + (mid - a[i] - 1)) / (mid - a[i]);
cnt = x * (mid - a[i]) - (b[i] - cnt);
ans += x;
}
}
return ans <= k;
}
signed main() {
cin >> n >> k;
f(i,1,n){
cin >> a[i];
}
f(i,1,n){
cin >> b[i];
}
int l = a[n] + 1,r = 1e16,ans = -1;
while (l <= r){
int mid = (l + r) / 2;
if (check(mid)){
r = mid - 1;
ans = mid;
}
else{
l = mid + 1;
}
}
cout << ans;
return 0;
}
浙公网安备 33010602011771号