第 2 章 二分与三分

10 篇文章 0 订阅
9 篇文章 0 订阅

例1:愤怒的牛

类型 二分
题目

这里写图片描述

题解

$ 这类题目用二分,有点像在凑答案(因为答案很难从正面求解,但是可以确定一个数字能否成为答案),凑的时候发现刚好一半能符合,另一半不符合,于是通过二分查找这个分界点。$

代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int x[maxn],n,m,L,R,mid;
bool check(int s){for(int i=2,lst=x[1];i<=n&&s>0;++i)if(x[i]-lst>=mid)if((lst=x[i])&&(--s<2))return true;return false;}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;++i) scanf("%d",x+i);
	sort(x+1,x+n+1);
	for (L=0,R=1e9,mid=R>>1;L<=R;mid=L+R>>1)
	  if (check(m)) L=mid+1;else R=mid-1;
	printf("%d",R);return 0;
}



例2:Best Cow Fences

类型 二分
题目

这里写图片描述

题解

$ 这题起点和长度都不确定,而且都会影响答案,我们可以通过枚举这两个 O(N^2) 求解。$
$ 我们手里有三个变量:序列起点 le,序列长度 len,平均数 ans。$
$ 我们的任务是求长度不短于 L 的序列的最大平均值$
$ 其中序列长度只要满足 \geq L 就好了,具体不需要知道,同时使得平均数最大$

$ \frac{\sum_{i=le}^{le+len-1} a[i]}{len} \leq ans$

$ \sum_{i=le}^{le+len-1} a[i] \leq ans*len$

$ \sum_{i=le}^{le+len-1} a[i]-ans \leq 0 $

$ 我们假设知道平均数,然后对序列每个元素减去这个数,刷一趟长度 \geq L的最大连续子段求和,看看能否 \geq 0。$
$ 如果可以,说明这个平均数是可以达到的,否则说明不能达到。$

代码
#include<bits/stdc++.h>
using namespace std;
int n,len;double L,R,s[100005],f[100005],a[100005];
bool check(double de)
{
	for (int i=1;i<=n;++i) s[i]=s[i-1]-de+a[i],f[i]=max(f[i-1],0.0)+a[i]-de;
	for (int i=len;i<=n;++i) if (s[i]-s[i-len+1]+f[i-len+1]>=0) return true;
	return false;
}
int main()
{
	scanf("%d%d",&n,&len);L=3e9,R=-3e9;
	for (int i=1,x;i<=n;++i)scanf("%lf",&a[i]),L=min(L,a[i]),R=max(R,a[i]);
	for (double mid=(L+R)/2;R-L>=1e-8;mid=(L+R)/2)if (check(mid)) L=mid;else R=mid;
	printf("%lld",(long long)(R*1000));return 0;
}



例3:曲线

类型 三分
题目

这里写图片描述

题解

$ 由于所有函数不是一次函数就是开口向上的二次函数,所以组合成的函数也是一个开口向上的函数,直接三分即可$

代码
#include<bits/stdc++.h>
#define p(x,y) ((x+y)/2.0)
using namespace std;
const int maxn=1e5+5;
const double EXP=1e-10;
int n,a[maxn],b[maxn],c[maxn];
double ans;
int rad()
{
	int ret=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
	return ret*f;
}
double check(double x)
{
	double sum=-1e18;
	for (int i=1;i<=n;++i) sum=max(sum,x*x*a[i]+x*b[i]+c[i]);
	if (sum<ans) ans=sum;return sum;
}
void pd(double y1,double y2,double &L,double &R,double &mid)
{
	if (y1<y2) R=p(mid,R);else
	if (y1>y2) L=mid;else L=mid,R=p(mid,R);
	mid=p(L,R);
}
int main()
{
	for (int T=rad();T;--T)
	{
		n=rad();ans=1e18;
		for (int i=1;i<=n;++i) a[i]=rad(),b[i]=rad(),c[i]=rad();
		for (double L=0,R=1000,mid=p(L,R);R-L>EXP;pd(check(mid),check(p(mid,R)),L,R,mid));
		printf("%.4lf\n",ans);
	}
	return 0;
}



练习1:数列分段 II

类型 二分
题目

这里写图片描述

题解

$ 同例1$

代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int n,m;
long long a[maxn];
int rad()
{
	int ret=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
	return ret*f;
}
int nxt(int L,long long p)
{
	int s=-1;for (int x=L-1,R=n,mid=L+R>>1;L<=R;mid=L+R>>1)
	  if (a[mid]-a[x]>p) R=mid-1;else
	  if (a[mid]-a[x]<=p) L=mid+1,s=mid;
	return s;
}
bool check(long long p){int x=1;for (int i=1;i<=m;++i) if ((x=nxt(x,p)+1)>n) return true;return false;}
int main()
{
	n=rad();m=rad();int ans;
	for (int i=1;i<=n;++i) a[i]=rad()+a[i-1];
	for (long long L=0,R=a[n],mid=L+R>>1;L<=R;mid=L+R>>1)
	  if (check(mid)) R=mid-1,ans=mid;else L=mid+1;
	printf("%d",ans);return 0;
}



练习2:扩散

类型 二分
题目

这里写图片描述

题解

$ 我们可以判断哪些点在某一时刻是否连接,加入我们知道一个时刻,就能使得它是否符合要求$
$ 全部连通后不可能会变成不连通的状态,所以二分时间,并查集判断即可。$

代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=55;
int n,x[maxn],y[maxn],fa[maxn];
int rad()
{
	int ret=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
	return ret*f;
}
int get(int x){return x==fa[x]?x:fa[x]=get(fa[x]);}
int ab(int x){return x<0?-x:x;}
void merge(int x,int y){x=get(x);y=get(y);fa[x]=x==y?fa[x]:y;}
int main()
{
	n=rad();
	for (int i=1;i<=n;++i) x[i]=rad(),y[i]=rad();
	int L=0,R=2e9/2+1,ans=0;
	while (L<=R)
	{
		int mid=(R-L>>1)+L,pd=1;
		for (int i=1;i<=n;++i) fa[i]=i;
		for (int i=2;i<=n;++i)
		 for (int j=1;j<i;++j)
		   if (ab(x[i]-x[j])+ab(y[i]-y[j])+1>>1<=mid) merge(i,j);
		int F=get(1);
		for (int i=2;i<=n;++i) if (get(i)!=F) {pd=0;break;}
		if (pd==1) R=mid-1;else L=mid+1;
	}
	printf("%d",L);
	return 0;
}



练习3:灯泡

类型 三分 | 数学
题目

这里写图片描述

题解

$ 假设灯的坐标为(0,H),若人的脚位于(x,0),那么其它量都可以表示出来$
$ h,D已知。若影子投到墙上,L=D-x+\frac{(H-h)*D}x+H=\frac{(H-h)D}x-x+D+H,否则 \frac{D-x}h=\frac xH,则 L=D-hD/h 为最大值 $
证明1

$ 这个函数类似双勾函数,直接在上面三分即可$

$ 网上大神有O(1)推出来的。_{不包括本蒟蒻}^{不包括本蒟蒻}$

$ 当x = \sqrt{H-h} 时, \frac{(H-h)D}x-x 为最大值,但是 x 可能去不得那个值(范围限制 x\in[D-\frac{Dh}H,D]),需特判。$
$ 若 x > D,则最大值为 h。若 x < D-\frac{Dh}H,L则为 D-x = \frac{Dh}H 。$

代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
	int T;scanf("%d",&T);
	while (T--)
	{
		double H,h,D,x;scanf("%lf%lf%lf",&H,&h,&D);
		if ((x=sqrt((H-h)*D))>D) printf("%.3lf\n",h);else
		if (x<(H-h)*D/H) printf("%.3lf\n",h*D/H);else
		printf("%.3lf\n",D+H-x+(h-H)*D/x);
	}
	return 0;
}



练习4:传送带

类型 三分、数学
题目

这里写图片描述

题解

$ 笨蛋的想法是枚举两条直线上的点,分三段一求就好了。$
$ 仔细思考会发点,离开AB的点确定,到达CD的点的位置和答案的关系应类似于一个开口向上的函数,可以三分查找最低点$
$ 然而离开AB的点也可以三分枚举,我们就按照坐标进行三分即可。$

代码
#include<bits/stdc++.h>
#define DB double
using namespace std;
const DB EXP=1e-5;
struct pt{DB x,y;};
pt L1,L2,R1,R2,mid1,mid2,A,B,C,D,A1,B1,C1,D1;
DB v1,v2,v3,k1,k2;
void rd(pt &p){scanf("%lf%lf",&p.x,&p.y);}
DB ab(DB x){return x<0?-x:x;}
DB sq(DB x){return x*x;}
DB dis(pt a,pt b){return sqrt(sq(a.x-b.x)+sq(a.y-b.y));}
pt get(pt a,pt b){pt c;c.x=(a.x+b.x)/2.0;c.y=(a.y+b.y)/2.0;return c;}
DB work(pt AB)
{
	C1=C;D1=D;
	DB sum=dis(AB,D)/v3;
	for (pt mid=get(C1,D1);ab(C1.x-D1.x)>EXP||ab(C1.y-D1.y)>EXP;mid=get(C1,D1))
	{
		pt mid2=get(mid,D1);
		DB p1=dis(mid ,D)/v2+dis(AB,mid )/v3,
		   p2=dis(mid2,D)/v2+dis(AB,mid2)/v3;
		if (sum>p1)sum=p1;
		if (sum>p2)sum=p2;
		if (p1<p2) D1=mid2;else
		if (p1>p2) C1=mid ;else
		C1=mid,D1=mid2;
	}
	return sum+dis(A,AB)/v1;
}
int main()
{
	rd(A);rd(B);rd(C);rd(D);
	scanf("%lf%lf%lf",&v1,&v2,&v3);
	if (v3>=v1&&v3>=v2) {printf("%.2lf",dis(A,D)/v3);return 0;}
	DB ans=work(A);
	A1=A;B1=B;
	for (pt mid=get(A1,B1);ab(A1.x-B1.x)>EXP||ab(A1.y-B1.y)>EXP;mid=get(A1,B1))
	{
		pt mid2=get(mid,B1);
		DB p1=work(mid),p2=work(mid2);
		if (ans>p1)ans=p1;
		if (ans>p2)ans=p2;
		if (p1<p2) B1=mid2;else
		if (p1>p2) A1=mid ;else
		A1=mid,B1=mid2;
	}
	printf("%.2lf",ans);
	return 0;
}

这里写图片描述
$ 相似三角形,\frac DH=\frac {D-x}h$
$ ∴ \frac{Dh}H = D-x$
$ ∴ x = D-\frac{D
h}H$
$ 当 x < D-\frac{Dh}H 时影子不在墙上$
这里写图片描述
$ 当 影子在墙上时,L = D-x + 绿色部分$
$ 绿色部分 = 橙色的一次函数在D处的值$
$ 一次函数 f(A)=(H-h)/x
A+H$
$ 绿色部分 = (H-h)/x*D+H$
$ 加起来即可$


  1. ↩︎
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值