三分模板
整数三分
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,m−1] 件,所以这题可以用二分,把求值问题转化为验证问题,关键在于如何验证枚举的
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
mid−k 件,根据题意得到如下两个不等式:
2
k
+
4
(
m
i
d
−
k
)
<
=
x
①
2k+4(mid-k) <=x \ ①
2k+4(mid−k)<=x ①
3
k
+
(
m
i
d
−
k
)
<
=
y
②
3k+ (mid-k) <= y \ ②
3k+(mid−k)<=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<=24∗mid−x<=k<=2y−mid<=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(4x−2∗k,y−3∗k)
单峰函数,根据三分法,我们就可以缩小该单峰区间的极值范围,然后缩小到一定范围之后,我们进行枚举,取出最大值就好了。
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,a3…an,可以选一个实数
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
a1−x,a2−x,a3−x…an−x
数列的值为子段绝对值的最大值,求一个数列可得的最小值。
思路:
假设这个值为
m
a
x
(
S
k
−
x
k
)
max(S_k - x_k)
max(Sk−xk)。
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(∣Sk−x∗k∣) 的图像为单调函数按照某个点对称翻折后的图像,明显是单峰的。
每次
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;
}

被折叠的 条评论
为什么被折叠?



