// // // // // // // // // // // // // //

二分与三分

前言

最近做的二分与三分的题目

做的好像都是板子 不是板子的题目用一种 很诡异 很巧妙的方法水了过去


1. 愤怒的牛

题意简化:

m头牛放入一条直线上的n间牛舍 最大化最小距离

思路:

二分板子

code:

/*
  Time: 12.20
  Worker: Blank_space
  Source: #10011. 「一本通 1.2 例 1」愤怒的牛
*/
/*--------------------------------------------*/
#include<cstdio>
#include<algorithm>
using namespace std;
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int FFF = 0x8fffffff;
/*------------------------------------常量定义*/
int n, m, a[B], c[B], ans;
/*------------------------------------变量定义*/
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 << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
/*----------------------------------------快读*/
bool check(int x)
{
	int sum = 0, num = 1;
	for(int i = 2; i <= n; i++)
	{
		if(sum + c[i] < x) sum += c[i];
		else {sum = 0; num++;}
	}
	return num >= m;
}
/*----------------------------------------函数*/
int main()
{
    n = read(); m = read(); int l = 0, r = 0; 
    for(int i = 1; i <= n; i++) a[i] = read(), r = max(r, a[i]);
    sort(a + 1, a + 1 + n);
    for(int i = 1; i <= n; i++) c[i] = a[i] - a[i - 1];
    while(l <= r)
    {
    	int mid = (l + r) >> 1;
    	if(check(mid))
    	{
    		ans = mid;
    		l = mid + 1;
    	}
    	else r = mid - 1;
    }
    printf("%d", ans);
	return 0;
}

2. Best Cow Fences

题意简化:

给定一个长度为 \(n\) 的非负整数序列\(A\) ,求一个平均数最大的,长度不小于 \(L\)的子段

思路:

二分平均数 将原序列中所有的数减去这个平均数 转换为判断一个子段非负 也可以说是求最大子段和

枚举到第 i 个数时 用第 i 数减去 第 1 ~ i - L 个数中最小值 统计最大值

因为已经减去了平均值 所以最后判断这个最大子段和是否非负

code:

/*
  Time: 12.20
  Worker: Blank_space
  Source: #10012. 「一本通 1.2 例 2」Best Cow Fences
*/
/*--------------------------------------------*/
#include<cstdio>
#include<algorithm>
using namespace std;
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int FFF = 0x8fffffff;
/*------------------------------------常量定义*/
int n, L;
double a[B], b[B], sum[B];
/*------------------------------------变量定义*/
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 << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
/*----------------------------------------快读*/
bool check(double x)
{
	double ans = -INF, minn = INF;
	for(int i = 1; i <= n; i++) b[i] = a[i] - x, sum[i] = sum[i - 1] + b[i];
	for(int i = L; i <= n; i++)
	{
		minn = min(minn, sum[i - L]);
		ans = max(ans, sum[i] - minn);
	}
	return ans > 0;
}
/*----------------------------------------函数*/
int main()
{
    n = read(); L = read();
    for(int i = 1; i <= n; i++) a[i] = read();
    double eps = 1e-5, l = -1e6, r = 1e6;
    while(r - l > eps)
    {
    	double mid = (l + r) / 2;
    	if(check(mid)) l = mid;
    	else r = mid;
    }
    printf("%d", int(r * 1e3));
	return 0;
}

3. 曲线

题意简化:

求n个二次函数在区间[1, n]上的最大值

思路:

三分板子

code:

/*
  Time: 12.20
  Worker: Blank_space
  Source: #10013. 「一本通 1.2 例 3」曲线
*/
/*--------------------------------------------*/
#include<cstdio>
#include<algorithm>
#include<string.h>
#define emm(x) memset(x, 0, sizeof x)
using namespace std;
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int FFF = 0x8fffffff;
/*------------------------------------常量定义*/
int T, n;
struct node {
	double a, b, c;
}a[B];
/*------------------------------------变量定义*/
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 << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
/*----------------------------------------快读*/
double f(double x)
{
	double _ans = -INF;
	for(int i = 1; i <= n; i++) _ans = max(_ans, a[i].a * x * x + a[i].b * x + a[i].c);
	return _ans;
}
void work()
{
	n = read(); emm(a); double l = 0, r = 1000;
	for(int i = 1; i <= n; i++) {a[i].a = read(); a[i].b = read(); a[i].c = read();}
	while(l + 1e-10 < r)
	{
		double m1 = l + (r - l) / 3, m2 = r - (r - l) / 3;
		if(f(m1) > f(m2)) l = m1;
		else r = m2;
	}
	printf("%.4f\n", f(l));
}
/*----------------------------------------函数*/
int main()
{
    T = read();
    while(T--) work();
	return 0;
}

4. 数列分段II

题意简化:

长度为\(n\)的正整数数列 分为连续m段 最小化每一段的最大值

思路:

二分每一区间的最大值 进行检查

二分答案板子

code:

/*
  Time: 12.20
  Worker: Blank_space
  Source: #10014. 「一本通 1.2 练习 1」数列分段 II
*/
/*--------------------------------------------*/
#include<cstdio>
#include<algorithm>
using namespace std;
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int FFF = 0x8fffffff;
/*------------------------------------常量定义*/
int n, m, a[B], s, ans, maxx;
/*------------------------------------变量定义*/
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 << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
/*----------------------------------------快读*/
bool check(int x)
{
	int sum = 0, num = 1;
	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()
{
//	freopen("divide_b4.in", "r", stdin);
    n = read(); m = read();
    for(int i = 1; i <= n; i++) a[i] = read(), s += a[i], maxx = max(a[i], maxx);
    int l = maxx, r = s;
    while(l <= r)
    {
    	int mid = (l + r) >> 1;
    	if(check(mid))
    	{
    		ans = mid;
    		r = mid - 1;
    	}
    	else l = mid + 1;
    }
    printf("%d", ans);
	return 0;
}

5. 扩散

题意简化:

在一个二维空间内 给定 n 个点 一个点每过一个单位的时间就会向4个方向扩散一个距离 两个点的扩散区域有重合时称两个点联通 求n个点形成一个连通块时的最小时间

思路:

当两个点中间没有其他点时 这两个点联通的最小时间就是这两个点的曼哈顿距离除以二

由此来判断二分出的时间是否合法

对于点与点之间的联通 采用并查集来实现

code:

/*
  Time: 12.20
  Worker: Blank_space
  Source: #10015. 「一本通 1.2 练习 2」扩散
*/
/*--------------------------------------------*/
#include<cstdio>
#include<algorithm>
using namespace std;
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int FFF = 0x8fffffff;
/*------------------------------------常量定义*/
int n, maxx, maxy, minx = INF, miny = INF, fa[100], ans, cnt;
struct node {
	int x, y;
}a[100];
/*------------------------------------变量定义*/
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 << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
/*----------------------------------------快读*/
int find(int x) { if(fa[x] != x) return fa[x] = find(fa[x]); return x;}
bool check(int x)
{
	for(int i = 1; i <= n; i++) fa[i] = i; cnt = 0;
	for(int i = 1; i <= n; i++)
		for(int j = i + 1; j <= n; j++)
			if(abs(a[i].x - a[j].x) + abs(a[i].y - a[j].y) <= x * 2)
			{
				int xx = find(i), yy = find(j);
				if(xx != yy) fa[xx] = yy;
			}
	for(int i = 1; i <= n; i++)
		if(fa[i] == i) cnt++;
	return cnt == 1;
}
/*----------------------------------------函数*/
int main()
{
    n = read();
    for(int i = 1; i <= n; i++)
	{
		a[i].x = read();a[i].y = read();
		maxx = max(maxx, a[i].x); maxy = max(maxy, a[i].y);
		minx = min(minx, a[i].x); miny = min(miny, a[i].y);
	}
    int l = 0, r = maxx + maxy - minx - miny;
    while(l <= r)
    {
    	int mid = (l + r) >> 1;
    	if(check(mid))
    	{
    		ans = mid;
    		r = mid - 1;
    	}
    	else l = mid + 1;
    }
    printf("%d", ans);
	return 0;
}

6. 灯泡

题目描述

相比 wildleopard 的家,他的弟弟 mildleopard 比较穷。他的房子是狭窄的而且在他的房间里面仅有一个灯泡。每天晚上,他徘徊在自己狭小的房子里,思考如何赚更多的钱。有一天,他发现他的影子的长度随着他在灯泡和墙壁之间走到时发生着变化。一个突然的想法出现在脑海里,他想知道他的影子的最大长度。

bulb.jpg

思路:

我感觉这是个结论题

这是什么二分啊 这不就是一个数学题吗

所以用一种很 诡异 巧妙的方法水过

但我的复杂度是 \(O(1)\)

首先 根据题目:

灯高 H

人高 h

灯距墙壁的距离为 D

人的影子长度为 L

考虑表示人的影子长度

设:

人与灯的距离为 x

影子落在墙上(如果有的话)的部分为 y

墙上的那部分影子如果再落在地上所能产生的影子的长度为 t

如图:

(下面的推导过程我建议把图画下来对着图理解)

然后 根据影子是否落在墙上分类讨论

一: 影子没有落在墙上

此时 根据相似三角形可以直接得出:

\[\frac{h}{H} = \frac{L}{x + L} \]

化简 得:

\[L = \frac{xh}{H - h} \]

这个时候 因为要保证影子不会落在墙上 所以 有:

\[x + L \leq D \]

将 L 代入 得:

\[x + \frac{xh}{H - h} \leq D \]

化简 得:

\[x \leq \frac{(H - h)D}{H} \]

所以 x 的范围:

\[0\leq x \leq \frac{(H - h)D}{H} \]

二: 影子有一部分落在墙上

由图 我们不难看出:

\[L = D - x + y \]

此时 根据相似三角形 有:

\[\frac{D + t}{H} = \frac{L - y + t}{h} = \frac{t}{y} \]

设出的为自变量 想办法让 x 进入这个式子

观察一下图 可以列出:

\[L - y + t = D - x + t \]

所以上面的式子又可以化为:

\[\frac{D + t}{H} = \frac{D - x + t}{h} = \frac{t}{y} \]

将式子拆开来看

首先是左边两个:

\[\frac{D + t}{H} = \frac{D - x + t}{h} \]

化简 得:

\[t = \frac{DH - xH - hD}{h - H} \]

我们就有了 t 与 x 的关系

再看右边:

\[\frac{D - x + t}{h} = \frac{t}{y} \]

化简 得:

\[y = \frac{ht}{D - x + t} \]

然后将 t 代入 得:

\[y = \frac{h\frac{DH - xH - hD}{h - H}}{D - x + \frac{DH - xH - hD}{h - H}} \]

化简 得 :

\[y = \frac{hD + xH - DH}{x} \]

x 与 y 的关系也有了

回到上面的式子:

\[L = D - x + y \]

代入 y 得:

\[L = D - x + \frac{hD + xH - DH}{x} \]

化简 得:

\[L = D - x + H + \frac{D(h - H)}{x} \]

结合上一种情况 此时 x 的范围为:

\[\frac{(H - h)D}{H} < x \leq D \]

整理这两种情况 就有了L 关于 x 的函数:

\[L = \begin{cases}\frac{xh}{H - h},0\leq x \leq \frac{(H - h)D}{H}\\ D - x + H + \frac{D(h - H)}{x},\frac{(H - h)D}{H} < x \leq D \end{cases} \]

所以有什么用呢?

上面的那个式子 很明显是单调递增的 所以最大值在右端点取到

下面的那个式子 再观察一下:

\[L = D - x + H + \frac{D(h - H)}{x} = D + H - (x + \frac{D(H - h)}{x}) \]

这就很明显了

后面那一坨东西就是对勾函数

那么:

\[x + \frac{D(H - h)}{x}\geq 2\sqrt{D(H - h)} \]

所以:

\[L = D - H - (x + \frac{D(H - h)}{x})\\ \leq D + H - 2\sqrt{D(H - h)} \]

当且仅当:

\[x = \sqrt{D(H - h)} \]

时 L 取到最大值

这时我们发现 这个 x 到底能不能取到是一个问题

对于这一问题 我们再次分类讨论

一:

\[\sqrt{D(H - h)}\leq \frac{(H - h)D}{H} \]

因为前面的那个式子是单调的

所以此时的答案就为:

\[L= \frac{xh}{H - h} = \frac{H - h}{D} \frac{h}{H - h} = \frac{hD}{H} \]

二:

\[\frac{(H - h)D}{H}<\sqrt{D(H - h)} \leq D \]

将两个取值范围的最值进行比较 取较大值

即 比较:

\[D + H - 2\sqrt{D(H - h)} \text{与}\frac{hD}{H} \]

的大小

三:

\[\sqrt{D(H - h)} > D \]

此时 \((0 , D]\) 这一区间内 函数是单调递增的

所以最大值在 \(x = D\) 处取到

此时 最大值为:\(h\)

再与\(\frac{hD}{H}\) 进行比较即可

至此 对于每一次的询问 则可以 \(O(1)\) 的进行回答

code:

/*
  Time: 12.20
  Worker: Blank_space
  Source: #10016. 「一本通 1.2 练习 3」灯泡
  要什么二分三分的 这就是个结论题 
*/
/*--------------------------------------------*/
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int FFF = 0x8fffffff;
/*------------------------------------常量定义*/
int T;
double H, h, D;
/*------------------------------------变量定义*/
void work()
{
	cin >> H >> h >> D;
	printf("%.3f\n", (sqrt(D * (H - h)) < (H - h) * D / H) ? (D * h / H) : (max(h * D / H, (sqrt(D * (H - h)) > D) ? h : (D + H - 2 * sqrt(D * (H - h))))));
}
/*----------------------------------------函数*/
int main()
{
    scanf("%d", &T);
    while(T--) work();
	return 0;
}

posted @ 2020-12-20 20:20  Blank_space  阅读(159)  评论(3编辑  收藏  举报
// // // // // // //