二分+三分练习

三分模板

整数三分

int l = 1,r = 100;
while(l < r) {
    int lmid = l + (r - l) / 3;
    int rmid = r - (r - l) / 3;
    lans = f(lmid),rans = f(rmid);
    // 求凹函数的极小值
    if(lans <= rans) r = rmid - 1;
    else l = lmid + 1;
    // 求凸函数的极大值
    if(lasn >= rans) l = lmid + 1;
    else r = rmid - 1;
}
// 求凹函数的极小值
cout << min(lans,rans) << endl;
// 求凸函数的极大值
cout << max(lans,rans) << endl;

浮点三分

const double EPS = 1e-9;
while(r - l < EPS) {
    double lmid = l + (r - l) / 3;
    double rmid = r - (r - l) / 3;
    lans = f(lmid),rans = f(rmid);
    // 求凹函数的极小值
    if(lans <= rans) r = rmid;
    else l = lmid;
    // 求凸函数的极大值
    if(lans >= rans) l = lmid;
    else r = rmid;
}
// 输出 l 或 r 都可
cout << l << endl;

牛客专题班传送门

B题

完全平方数
题意:查询 [ l , r ] [l,r] [l,r] 范围内的完全平方数个数
思路:
这个题需要的思路就比较巧,考虑 1000000000 = 31622 \sqrt{1000000000}=31622 1000000000 =31622,总范围内至多这些完全平方数
对查询区间 l , r l,r l,r 也开方,然后二分出来左边界和右边界即可
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
#define N 31622
int a[N + 9];
void work()
{
	int l, r;
	cin >> l >> r;
	l = lower_bound(a, a + N + 1, sqrt(l)) - a;
	r = upper_bound(a, a + N + 1, sqrt(r)) - a;
	cout << max(r - l, 0) << endl;
}

int main()
{
	ios::sync_with_stdio(0);
	for(int i = 0; i <= 31622; ++i) a[i] = i;
	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

C题

Aggressive cows
二分经典题目,分配牛到牛舍
code:

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#define endl '\n'
#define ll long long
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
int x[maxn];
bool check(int mid)
{
	int last = 1;
	for(int i = 2; i <= m; ++i)
	{
		int now = last + 1;
		while(now <= n && x[now] - x[last] < mid) ++now;
		if(now > n) return 0;
		last = now;
	}
	return 1;
}
void work()
{
	cin >> n >> m;
	for(int i = 1; i <= n; ++i) cin >> x[i];
	sort(x + 1, x + 1 + n);
	int l = 0, r = 1e9;
	while(l < r)
	{
		int mid = (l + r) >> 1;
		if(!check(mid)) r = mid;
		else l = mid + 1;
	}
	if(check(l-1)) cout << l-1<< endl;
	else cout << l << endl;
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

P题

装备合成
题意:
给定 x , y x,y x,y 代表 x x x 件材料 a a a y y y 件材料 b b b,两个合成方式:
2 a + 3 b 2a+3b 2a+3b 或者 4 a + b 4a+b 4a+b
求最多合成数量
和G题一样
二分思路:
不难看出,答案具有单调性,当能合成 m m m 件装备时也一定能合成 [ 0 , m − 1 ] [0,m-1] [0,m1] 件,所以这题可以用二分,把求值问题转化为验证问题,关键在于如何验证枚举的 m i d mid mid 是否满足条件。
对于每 m i d mid mid 件装备,假定方式 1 1 1 合成了 k k k 件,则方式 2 2 2 合成了 m i d − k mid-k midk 件,根据题意得到如下两个不等式:
2 k + 4 ( m i d − k ) < = x  ① 2k+4(mid-k) <=x \ ① 2k+4(midk)<=x 
3 k + ( m i d − k ) < = y  ② 3k+ (mid-k) <= y \ ② 3k+(midk)<=y 
然后化简一下这个不等式
0 < = 4 ∗ m i d − x 2 < = k < = y − m i d 2 < = m i d 0<=\frac{4*mid-x}{2}<=k<=\frac{y-mid}{2}<=mid 0<=24midx<=k<=2ymid<=mid
c h e c k check check 即可
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
ll x, y;
bool check(ll ans)
{
	ll x1 = floor(1.0 * (y - ans) / 2);
	ll x2 = ceil(1.0 * (4 * ans - x) / 2);
	x1 = min(x1, ans); x2 = max(x2, 0ll);
	return x1 >= x2;
}
void work()
{
	cin >> x >> y;
	ll l = 0, r = 1e9;
	while(l < r)
	{
		ll mid = (l + r + 1) >> 1;
		if(check(mid)) l = mid;
		else r = mid - 1;
	}
	cout << r << endl;
}

int main()
{
	ios::sync_with_stdio(0);
	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

三分思路:
设方式一合成 k k k 个,那么 a n s = k + m i n ( x − 2 ∗ k 4 , y − 3 ∗ k ) ans=k+min(\frac{x-2*k}{4},y-3*k) ans=k+min(4x2k,y3k)
单峰函数,根据三分法,我们就可以缩小该单峰区间的极值范围,然后缩小到一定范围之后,我们进行枚举,取出最大值就好了。
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
ll x, y;
ll f(ll k)
{
	return k + min((x - 2 * k) / 4, y - 3 * k);
}
void work()
{
	cin >> x >> y;
	ll l = 0, r = min(x / 2, y / 3);
	while(l + 7 < r)
	{
		ll mid1 = l + (r - l) / 3;
		ll mid2 = r - (r - l) / 3;
		if(f(mid1) < f(mid2)) l = mid1;
		else r = mid2;
	}
	ll ans = 0;
	for(int i = l; i <= r; ++i)
		ans = max(ans, f(i));
	cout << ans << endl;
}

int main()
{
	ios::sync_with_stdio(0);
	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}


W题

SCOI2010-传送带
题意:
两个传送带,给定每个传送带的起点 A , C 和 A,C和 A,C 终点坐标 B , D B,D B,D,以及两个传送带和平地上运动速度,求 A A A 点到 D D D 点的最短时间
思路:
我们先三分出从 A A A 点到 A B AB AB 中的某个点 X X X,作为出发点,然后,再三分出从 X X X C D CD CD 的某个点 Y Y Y,再从 Y Y Y 直接到 D D D,这样,我们就可以求出最小的值了。
三分套三分
最短路径就是 A X + X Y + Y D AX+XY+YD AX+XY+YD
如何三分出点 Y Y Y
Y Y Y 必然在 线段 A B AB AB 上,可以三分 Y Y Y 点距离 A A A 点的长度,然后按比例写出坐标
还有注意卡精度!卡精度!卡精度!
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define eps 1e-4
#define double long double
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
double p, q, r;
struct node{
	double x, y;
}a, b, c, d, e;
double dis(node a, node b)
{
	return sqrt(eps + (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
}
double F(double len)
{
	node f = {c.x + (len + eps) / dis(c, d) * (d.x - c.x), c.y + (len + eps) / dis(c, d) * (d.y - c.y)};
	return (dis(e, f) + eps) / r + (dis(c, d) - len + eps) / q;
}
double f(double len)
{
	e = {a.x + (len + eps) / dis(a, b) * (b.x - a.x), a.y + (len + eps) / dis(a, b) * (b.y - a.y)};
	// 按比例求出 e 点坐标 
	double l = 0, r = dis(c, d);
	for(int i = 1; i <= 100; ++i)
	{
		double midl = l + (r - l) / 3, midr = r - (r - l) / 3;
		if(F(midl) - eps > F(midr)) l = midl;
		else r = midr;
	}
	return F(l) + (len + eps) / p;
}
void work()
{
	cin >> a.x >> a.y >> b.x >> b.y;
	cin >> c.x >> c.y >> d.x >> d.y;
	cin >> p >> q >> r;
	double l = 0, r = dis(a, b);
	for(int i = 1; i <= 100; ++i)
	{
		double midl = l + (r - l) / 3;
		double midr = r - (r - l) / 3;
		if(f(midl) - eps > f(midr)) l = midl;
		else r = midr;
	}
	cout << fixed << setprecision(2) << f(l) << endl;
}
int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

X题

[SHOI2017]期末考试
题目:
在这里插入图片描述

思路:
借用洛谷大佬的图解
在这里插入图片描述

设公布成绩时间为 x x x,不愉快度为 f ( x ) f(x) f(x)
手动分析 f ( x ) f(x) f(x) 的性质,不愉快度由两部分组成,两部分部分组成,一部分是随着 x x x 增加,学生产生的总不愉快度是递增的,操作二和操作三都是随着 x x x 的增加而递减的。
因此 f ( x ) f(x) f(x) 可能是一个开口向上的单谷函数
尝试三分最小值,结果 A C AC AC
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define eps 1e-4
#define ull unsigned long long
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
ll a, b, c;
ll t[maxn], num[maxn];
ull f(ull x)
{
	ll sum1 = 0, sum2 = 0, ans = 0;// sum1 统计需要提前的总天数, sum2 是可以用于操作一转化的天数
	for(int i = 1; i <= m; ++i)
	{
		if(num[i] >= x) sum1 += num[i] - x;
		else  sum2 += x - num[i];
	}
	if(b <= a) ans = sum1 * b;// 用操作二显然更优
	else// 操作一更优
	{
		if(sum2 >= sum1) ans = sum1 * a;// 超出的部分都可以用操纵一
		else ans = sum2 * a + (sum1 - sum2) * b;// 操作一剩下的用操作二
	}
	for(int i = 1; i <= n; ++i) if(t[i] < x)//  学生的不愉快度
		ans += (x - t[i]) * c;
	return ans;
}
void work()
{
	cin >> a >> b >> c >> n >> m;
	for(int i = 1; i <= n; ++i) cin >> t[i];
	for(int i = 1; i <= m; ++i) cin >> num[i];
	ll l = 1, r = *max_element(num + 1, num + 1 + m);
	while(l + 10 < r)
	{
		ll midl = l + (r - l) / 3, midr = r - (r - l) / 3;
		if(f(midl) >= f(midr)) l = midl;
		else r = midr;
	}
	ull ans = 9e18;
	for(ll i = l; i <= r; ++i)
		ans = min(ans, f(i));
	cout << ans << endl;
}
int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

C. Weakness and Poorness
题意:
数列 a 1 , a 2 , a 3 … a n a_1,a_2,a_3…a_n a1,a2,a3an,可以选一个实数 x x x 使得数列变成 a 1 − x , a 2 − x , a 3 − x … a n − x a_1-x,a_2-x,a_3-x…a_n-x a1x,a2x,a3xanx
数列的值为子段绝对值的最大值,求一个数列可得的最小值。
思路:
假设这个值为 m a x ( S k − x k ) max(S_k - x_k) max(Skxk) S k S_k Sk 代表某长度为 k k k 的子段。
随着 x x x 的增大数列各元素的值会越来越小,很明显是随 x x x 单调递减的。
那么 m a x ( ∣ S k − x ∗ k ∣ ) max(|S_k - x*k|) max(Skxk) 的图像为单调函数按照某个点对称翻折后的图像,明显是单峰的。
每次 c h e c k check check 就是求 x = m i d x=mid x=mid 时的子段绝对值的最大值
这个可以用 d p dp dp 来求
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 2e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
double a[maxn];
double dp[maxn][2];// 0维护子段最大值,1维护子段最小值

double f(double x){
	double Max = -inf, Min = inf;
	dp[0][0] = -inf, dp[0][1] = inf;
	for(int i = 1; i <= n; ++i){
		double b = a[i] - x, c = x - a[i];
		dp[i][0] = max(dp[i-1][0] + b, b);
		dp[i][1] = min(dp[i-1][1] + b, b);
		Max = max(Max, dp[i][0]);
		Min = min(Min, dp[i][1]);
	}
	return max(Max, -Min);
}
void work()
{
	cin >> n;
	for(int i = 1; i <= n; ++i) cin >> a[i];
	double l = -1e4, r = 1e4;
	for(int i = 1; i <= 100; ++i){
		double mid1 = l + (r - l) / 3.0, mid2 = r - (r - l) / 3.0;
		if(f(mid1) < f(mid2)) r = mid2;
		else l = mid1;
	}
	cout << fixed << setprecision(8) << f(l);
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值