二分
对于一个存在单调性的函数,我们要枚举满足条件的最小/最大值,我们通过枚举中间值缩小范围来定位。
while(l<r)
{
mid=(l+r+1)>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
三分
对于一个存在单峰/单谷性的函数,我们可以通过枚举两个端点通过比较大小缩小范围求出峰顶/谷底。
while(r-l>1e-18)
{
lmid=l+(r-l)/3;
rmid=r-(r-l)/3;
if(check(lmid)>check(rmid)) l=lmid;
else r=rmid;
}
对于二分和三分,我重点会在例题中讲。
例题1:【Loj #10011. 「一本通 1.2 例 1」愤怒的牛】
题目链接
题目
农夫约翰建造了一座有 间牛舍的小屋,牛舍排在一条直线上,第 间牛舍在 的位置,但是约翰的
头牛对小屋很不满意,因此经常互相攻击。约翰为了防止牛之间互相伤害,因此决定把每头牛都放在离其它牛尽可能远的牛舍。也就是要最大化最近的两头牛之间的距离。
牛们并不喜欢这种布局,而且几头牛放在一个隔间里,它们就要发生争斗。为了不让牛互相伤害。John 决定自己给牛分配隔间,使任意两头牛之间的最小距离尽可能的大,那么,这个最大的最小距离是多少呢?农夫约翰建造了一座有 间牛舍的小屋,牛舍排在一条直线上,第 间牛舍在 的位置,但是约翰的
头牛对小屋很不满意,因此经常互相攻击。约翰为了防止牛之间互相伤害,因此决定把每头牛都放在离其它牛尽可能远的牛舍。也就是要最大化最近的两头牛之间的距离。
牛们并不喜欢这种布局,而且几头牛放在一个隔间里,它们就要发生争斗。为了不让牛互相伤害。John 决定自己给牛分配隔间,使任意两头牛之间的最小距离尽可能的大,那么,这个最大的最小距离是多少呢?
思路
首先我们对牛棚从小到大排序。
然后我们二分距离,再暴力检查是否可行。
时间复杂度:\(O(n\log n)\)
Code
// Problem: #10011. 「一本通 1.2 例 1」愤怒的牛
// Contest: LibreOJ
// URL: https://loj.ac/p/10011
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
//#define M
//#define mo
//#define N
int n, m, i, j, k;
int a[100010];
int l, r, mid, now;
int check(int d)
{
for(i=2, j=1, now=a[1]; i<=n; ++i)
if(a[i]-now>=d) ++j, now=a[i];
return j>=m;
}
signed main()
{
// freopen("tiaoshi.in", "r", stdin);
// freopen("tiaoshi.out", "w", stdout);
n=read(); m=read();
for(i=1; i<=n; ++i) a[i]=read();
sort(a+1, a+n+1);
l=0, r=a[n];
while(l<r)
{
mid=(l+r+1)>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
printf("%lld", l);
return 0;
}
例题2:【Loj #10012. 「一本通 1.2 例 2」Best Cow Fences】
题目链接
题目
给定一个长度为 nnn 的非负整数序列 AAA ,求一个平均数最大的,长度不小于 LLL 的子段。
思路
先二分平均值。
然后是判断。
如何判断一段数中是否存在长度大于等于 \(L\) 且平均值大于某个数的子段呢?
我们可以先让序列中的数都减去二分中的值,然后就转化为:
序列中是否存在长度大于等于 \(L\) 的字段和为正。
我们可以先构造一个前缀和。
假设我们当前算到序列中的第 \(i\) 项,我们只需要在前 \(i-L\) 项中找最小前缀和,再用 \(S_i\) 去减即可。如果为正,说明符合条件。
Code
// Problem: #10012. 「一本通 1.2 例 2」Best Cow Fences
// Contest: LibreOJ
// URL: https://loj.ac/p/10012
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
//#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
//#define M
//#define mo
#define N 100010
int n, m, i, j, k;
int len;
double l, r, mid, mn;
double sum[N], a[N];
int check(double d)
{
sum[0]=mn=99999999999999;
for(i=1; i<=n; ++i)
{
sum[i]=(i==1? double(0) : sum[i-1])+a[i]-d;
mn=min(mn, sum[max(0, i-len)]);
if(sum[i]-mn>0) return 1;
}
if(sum[n]>0) return 1;
return 0;
}
signed main()
{
// freopen("tiaoshi.in", "r", stdin);
// freopen("tiaoshi.out", "w", stdout);
n=read(); len=read();
for(i=1; i<=n; ++i) a[i]=double(read());
l=-1, r=2001;
while(r-l>0.000000001)
{
// printf("%lf %lf\n", l, r);
mid=(l+r)/double(2);
if(check(mid)) l=mid;
else r=mid;
}
printf("%d", int(r*1000));
return 0;
}
例题3:【Loj #10013. 「一本通 1.2 例 3」曲线】
题目链接
题目
明明做作业的时候遇到了 nnn 个二次函数 Si(x)=ax2+bx+cS_i(x)= ax^2 + bx + cSi(x)=ax2+bx+c,他突发奇想设计了一个新的函数 F(x)=max{Si(x)},i=1…nF(x) = max{S_i(x)}, i = 1ldots nF(x)=max{Si(x)},i=1…n。
明明现在想求这个函数在 [0,1000][0,1000][0,1000] 的最小值,要求精确到小数点后四位,四舍五入。
思路
三分模板。
在区间 \([0, 1000]\) 中进行三分,每次 \(O(n)\) 算出答案。
显然, 由于 \(s\) 函数满足单调性,\(f\) 函数是 \(s\) 函数中的最大值,必然也满足单调性。
Code
// Problem: #10013. 「一本通 1.2 例 3」曲线
// Contest: LibreOJ
// URL: https://loj.ac/p/10013
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
//#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
//#define N
//#define M
//#define mo
int n, m, i, j, k, t;
double l, r, lmid, rmid, sum;
double a[100010], b[100010], c[100010];
double check(double x)
{
sum=-99999999999;
for(i=1; i<=n; ++i)
sum=max(sum, a[i]*x*x+b[i]*x+c[i]);
return sum;
}
signed main()
{
// freopen("tiaoshi.in","r",stdin);
// freopen("tiaoshi.out","w",stdout);
t=read();
while(t--)
{
n=read();
for(i=1; i<=n; ++i)
scanf("%lf%lf%lf", &a[i], &b[i], &c[i]);
l=0; r=1000;
while(r-l>0.00000000001)
{
lmid=l+(r-l)/3;
rmid=r-(r-l)/3;
if(check(lmid)>check(rmid)) l=lmid;
else r=rmid;
}
printf("%.4lf\n", check(l));
}
return 0;
}
例题4:【P1182 数列分段 Section II】
题目链接
题目
对于给定的一个长度为N的正整数数列 \(A_{1\sim N}\),现要将其分成 \(M\)(\(M\leq N\))段,并要求每段连续,且每段和的最大值最小。
关于最大值最小:
例如一数列 \(4\ 2\ 4\ 5\ 1\) 要分成 \(3\) 段。
将其如下分段:
\[[4\ 2][4\ 5][1] \]第一段和为 \(6\),第 \(2\) 段和为 \(9\),第 \(3\) 段和为 \(1\),和最大值为 \(9\)。
将其如下分段:
\[[4][2\ 4][5\ 1] \]第一段和为 \(4\),第 \(2\) 段和为 \(6\),第 \(3\) 段和为 \(6\),和最大值为 \(6\)。
并且无论如何分段,最大值不会小于 \(6\)。
所以可以得到要将数列 \(4\ 2\ 4\ 5\ 1\) 要分成 \(3\) 段,每段和的最大值最小为 \(6\)。
思路
二分最小值,然后我们对于每次二分的值进行判断,于是就转换为:
给出一个序列,每段和的最大值不得超过 \(mid\),问最多能分出多少段。
如果段数超过 \(k\),说明可行。
时间复杂度 \(O(n\log n)\)
由于这道题是我2021.8做的,所以那时码风很现在的不太一样。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
int n,m,i,j,k;
int a[1000010];
int sum,now,t;
int l,r,mid;
int pan(int x)
{
now=0;
t=1;
for(i=1;i<=n;++i)
{
if(a[i]>x)
return 0;
if(now+a[i]>x)
{
now=a[i];
++t;
if(t>m)
return 0;
}
else
now+=a[i];
}
return 1;
}
signed main()
{
// freopen("seqseg.in","r",stdin);
// freopen("seqseg.out","w",stdout);
// std::ios::sync_with_stdio(false);
n=read();
m=read();
for(i=1;i<=n;++i)
a[i]=read(),sum+=a[i];
l=0,r=sum;
while(l<=r)
{
mid=l+r>>1;
if(pan(mid))
r=mid-1;
else
l=mid+1;
}
// for(int i=1;i<=sum;++i)
// printf("%d: %d\n",i,pan(i));
printf("%d",l);
return 0;
}
例题5:【P1661 扩散】
题目链接
题目
一个点每过一个单位时间就会向四个方向扩散一个距离,如图。两个点a、b连通,记作e(a,b),当且仅当a、b的扩散区域有公共部分。连通块的定义是块内的任意两个点u、v都必定存在路径e(u,a0),e(a0,a1),…,e(ak,v)。给定平面上的n给点,问最早什么时刻它们形成一个连通块。
思路
二分答案+并查集。
首先二分时间 \(t\)。
如果两个点能直接相连,则他们的曼哈顿距离小于二倍 \(t\)。并且把他们放到一个并查集里面。
最后判断所有点是否在一个并查集里即可。
Code
// Problem: P1661 扩散
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1661
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define N 55
//#define M
//#define mo
int n, m, i, j, k;
int x[N], y[N], f[N], l, r, mid;
int fa(int x)
{
if(f[x]==x) return x;
return f[x]=fa(f[x]);
}
int check(int t)
{
for(i=1; i<=n; ++i) f[i]=i;
for(i=1; i<=n; ++i)
for(j=i+1; j<=n; ++j)
if(abs(x[i]-x[j])+abs(y[i]-y[j])<=2*t)
f[fa(i)]=fa(j);
for(i=2; i<=n; ++i)
if(fa(i)!=fa(i-1)) return 0;
return 1;
}
signed main()
{
// freopen("tiaoshi.in","r",stdin);
// freopen("tiaoshi.out","w",stdout);
n=read();
for(i=1; i<=n; ++i)
x[i]=read(), y[i]=read();
l=1, r=1000000000000;
while(l<r)
{
mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%lld", l);
return 0;
}
例题6:【P5931 [清华集训2015]灯泡】
题目链接
题目
相比 \(Wildleopard\) 的家,他的弟弟 \(Mildleopard\) 比较穷。他的房子是狭窄的,而且在他的房间里仅有一个灯泡。每天晚上,他徘徊在自己狭小的房子里,思考如何赚更多的钱。有一天,他发现他的影子的长度随着他在灯泡和墙壁之间走动时会发生变化。一个突然的想法出现在他的脑海里,他想知道在房间里他的影子的最大长度。
思路
首先不考虑照在墙上,用相似三角形可以手推出 \(y=x\times \frac{h}{H-h}\)。
如果照在墙上,也可以再弄一个相似推出 \(y=D-x+H-\frac{D\times(H-h)}{x}\)。
其中,\(D-x\) 代表在地上的影子,\(H-\frac{D\times(H-h)}{x}\) 在墙上的影子。
易发现存在单谷性,可以用三分。
Code
#include<bits/stdc++.h>
using namespace std;
//#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
//#define N
//#define M
//#define mo
int n, m, i, j, k, t;
double H, h, D, l, r, lmid, rmid;
double check(double x)
{
if(x+x*h/(H-h)<=D) return x*h/(H-h);
return D-x+H-(H-h)*D/x;
}
signed main()
{
// freopen("tiaoshi.in","r",stdin);
// freopen("tiaoshi.out","w",stdout);
t=read();
while(t--)
{
scanf("%lf%lf%lf", &H, &h, &D);
l=0; r=D;
while(r-l>1e-11)
{
lmid=l+(r-l)/3;
rmid=r-(r-l)/3;
if(check(lmid)>check(rmid)) r=rmid;
else l=lmid;
}
printf("%.3lf\n", check(l));
}
return 0;
}
例题7:【P2571 [SCOI2010]传送带】
题目链接
题目
在一个 \(2\) 维平面上有两条传送带,每一条传送带可以看成是一条线段。两条传送带分别为线段 \(\text{AB}\) 和线段 \(\text{CD}\)。lxhgww 在 \(\text{AB}\) 上的移动速度为 \(P\),在 \(\text{CD}\) 上的移动速度为 \(Q\),在平面上的移动速度 \(R\)。现在 lxhgww 想从 \(\text A\) 点走到 \(\text D\) 点,他想知道最少需要走多长时间。
思路
三分套三分。
现在AB上三分一个点,再在CD上三分一个点,易证符合单调性。
求距离需要用到一些数学知识。
Code
#include<bits/stdc++.h>
using namespace std;
//#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
//#define M
//#define mo
//#define N
int n, m, i, j, k;
double ax, ay, bx, by, cx, cy, dx, dy, p, q, r;
double pingfa(double x)
{
return x*x;
}
double go(double lx, double ly, double rx, double ry, double speed)
{
double s=sqrt(pingfa(rx-lx)+pingfa(ry-ly));
return s/speed;
}
double second_three_mid(double lstlx, double lstly)
{
double lx, ly, rx, ry, tx, ty, nlx, nly, nrx, nry;
double lans, rans;
lx=cx; ly=cy; rx=dx; ry=dy;
while(abs(rx-lx)>1e-12||abs(ry-ly)>1e-12)
{
tx=(rx-lx)/3; ty=(ry-ly)/3;
nlx=lx+tx; nly=ly+ty;
nrx=rx-tx; nry=ry-ty;
lans=go(lstlx, lstly, nlx, nly, q)+go(nlx, nly, dx, dy, r);
rans=go(lstlx, lstly, nrx, nry, q)+go(nrx, nry, dx, dy, r);
// printf("> %lf %lf %lf %lf %lf %lf\n", lx, ly, rx, ry, lans, rans);
if(lans<rans) rx=nrx, ry=nry;
else lx=nlx, ly=nly;
}
// printf("======= %lf %lf========", go(lstlx, lstly, lx, ly, q), go(lx, ly, dx, dy, r));
return go(lstlx, lstly, lx, ly, q)+go(lx, ly, dx, dy, r);
}
double first_three_mid()
{
double lx, ly, rx, ry, tx, ty, nlx, nly, nrx, nry;
double lans, rans;
lx=ax; ly=ay; rx=bx; ry=by;
while(abs(rx-lx)>1e-12||abs(ry-ly)>1e-12)
{
// printf("> %lf %lf %lf %lf\n", lx, ly, rx, ry);
tx=(rx-lx)/3; ty=(ry-ly)/3;
nlx=lx+tx; nly=ly+ty;
nrx=rx-tx; nry=ry-ty;
lans=go(ax, ay, nlx, nly, p)+second_three_mid(nlx, nly);
rans=go(ax, ay, nrx, nry, p)+second_three_mid(nrx, nry);
// printf("\n%lf %lf %lf %lf %lf\n", nlx, nly, go(ax, ay, nlx, nly, p), second_three_mid(nlx, nly), lans);
// printf("\n%lf %lf %lf %lf %lf\n", nrx, nry, go(ax, ay, nrx, nry, p), second_three_mid(nrx, nry), rans);
if(lans<rans) rx=nrx, ry=nry/*, printf("right\n")*/;
else lx=nlx, ly=nly;
}
// printf("--- %lf %lf\n", go(ax, ay, lx, ly, p), second_three_mid(lx, ly));
return go(ax, ay, lx, ly, p)+second_three_mid(lx, ly);
}
signed main()
{
// freopen("tiaoshi.in", "r", stdin);
// freopen("tiaoshi.out", "w", stdout);
ax=double(read()); ay=double(read());
bx=double(read()); by=double(read());
cx=double(read()); cy=double(read());
dx=double(read()); dy=double(read());
if(ax==dx&&ay==dy) return printf("0.00"), 0;
p=double(read()); r=double(read()); q=double(read());
printf("%.2lf", first_three_mid());
return 0;
}