【ybtoj】二分算法例题
【基础算法】第三章 二分算法
例一 数列分段
题目描述
对于给定的一个长度为N的正整数数列A,现在将其分成M段,并要求每段连续,且每段和的最大值最小。
输入格式
第1行包含两个正整数N,M。
第2行包含N个空格隔开的非负整数A。
输出格式
仅包含一个正整数,即每段和最大值最小为多少。
样例输入
5 3 4 2 4 5 1
样例输出
6
分析
题中出现类似“最大值最小”的含义,这是答案具有单调性的最常见、最典型 的特征之一。设最优解为S,因为S的最优性如果要求每段和可以>S,那么一定存在一种划分方案使得总段数不超过M。因此答案就处于分段可行性的分界点上。
样例代码
bool check(int limit){ int cnt=1;sum=0; for(int i=1;i<=n;i++){ if(sum+a[i]<=n;i++){ sum+=a[i]; } else cnt++,sum=a[i]; } return cnt<=m; }
Code
#include<bits/stdc++.h> using namespace std; int n,m,a[100005],l,r,mid,ans; bool check(int x){ int sum=0,num=0; for(int i=1;i<=n;i++) { if(sum+a[i]<=x) sum+=a[i]; else sum=a[i],num++; } return num>=m; } int main(){ cin>>n>>m; for(int i=1;i<=n;i++) cin>>a[i],l=max(l,a[i]),r+=a[i]; while(l<=r){ mid=l+r>>1; if(check(mid)) l=mid+1; else r=mid-1; } cout<<l; return 0; }
例二 防具布置
题目描述
现在有N组防具。 我们可以认为防线是一维的,那么每一组防具都分布在防线的某一段上,并且同一组防具是等距离排列的。 也就是说,我们可以用三个整数 和 来描述一组防具,即这一组防具布置在防线的S,S+D,S+2D...S+KD位置上。 若一个位置上的防具数量为奇数,则我们称这个位置有破绽,但是整个防线上有且仅有一个位置有破绽或根本没有破绽。请你求出破绽的位置,或是确定防线没有破绽。
输入格式
第一行是一个整数T,表示有T组互相独立的测试数据。
每组数据的第一行是一个整数N。
之后N行,每行三个整数S,E,D,代表第i组防具的三个参数,数据用空格隔开。
输出格式
对于每组测试数据,如果防线没有破绽,输出一行 There's no weakness.。
否则在一行内输出两个空格分隔的整数P和C,表示在P位置 有C个防具。当然C应该是奇数。
样例输入
3 2 1 10 1 2 10 1 2 1 10 1 1 10 1 4 1 10 1 4 4 1 1 5 1 6 10 1
样例输出
1 1
There's no weakness.
4 3
分析
首先,若S(2^(31)-1)为偶数,则整道防线没有破绽。
否则,设破绽的位置为P,故只有P上有奇数个防具,其他位置上都有偶数个,则对于x<p,S(x)均为偶数,对于x>=P,S(x)均为奇数。
Code
#include <bits/stdc++.h> #define N 200010 using namespace std; int T, n; long long s[N], e[N], d[N], l, r, mid, ans; long long f(long long x){ long long sum=0; for (int i=1;i<=n;i++) if (s[i]<=x) sum+=(min(x,e[i])-s[i])/d[i]+1; return sum; } void work () { cin>>n; for (int i = 1; i <= n; i++) cin>>s[i]>>e[i]>>d[i]; if (f((long long)2147483647)%2==0){ printf("There's no weakness.\n"); return; } l=ans=1,r=(long long)2147483647; while (l < r) { mid = (l + r) / 2; if (f(mid) % 2 == 1) r = mid; else l=mid+1; } cout<<r<<" "<<f(r)-f(r-1)<<endl; } int main() { cin>>T; while(T--) work (); return 0; }
例三 最大均值
题目描述
给定正整数序列A,求一个平均数最大的,长度不小于L的(连续的)子段。
输入格式
第一行两个整数N和L。
接下来N行,每行输入一个正整数A。
输出格式
输出一个整数,表示平均值的最大值乘以1000再向下取整之后得到的结果。
样例输入
10 6 6 4 2 10 3 8 5 9 4 1
样例输出
6500
分析
注意到答案具有单调性,考虑二分答案,判定“是否存在一个长度不小于L的子段,平均值不小于mid”。
如果把序列里每个数都减去二分的值,就进一步转化为“是否存在一个长度不小于L的子段,子段和非负”。
然后只需要检查一下最大子段和是否为非负数,就可以确定二分上下界的变化范围了。
Code
#include <bits/stdc++.h> using namespace std; const double N=1e-5; int n,L; double a[100004],b[100004],sum[100004]; bool check(double num){ for(int i=1;i<=n;i++) b[i]=a[i]-num; for(int i=1;i<=n;i++) sum[i]=sum[i-1]+b[i]; double ans=-1e10; double v=1e10; for(int i=L;i<=n;i++){ v=min(v,sum[i-L]); ans=max(ans,sum[i]-v); } return ans>=0; } int main(){ double l=-1e6,r=1e6; cin>>n>>L; for(int i=1;i<=n;i++) cin>>a[i]; while(l+N<r){ double mid=(l+r)/2; if(check(mid)) l=mid; else r=mid; } cout<<int (r*1000)<<endl; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】