三分答案
三分答案入门
三分法求单峰(或者单谷)的极值,是二分法的一个简单扩展。
单峰函数和单谷函数如下图,函数 f(x) 在区间 [l,r] 内,只有一个极值 v ,在极值点两边,函数是单调变化的。以单峰函数为例,在 v 的左边,函数是严格单调递增的,在 v 右边是严格单调递减的。
图1 (1)单峰函数 (2)单谷函数
下面的讲解都以求单峰极值为例。
如何求单峰函数最大值的近似值?虽然不能直接用二分法,不过,只要稍微变形一下,就能用了。
在 [l,r] 上任取 2 个点,mid1 和 mid2 ,把函数分成 3 段。有以下情况:
(1)若 f(mid1) < f(mid2),极值点 v 一定在 mid1 的右侧。此时,mid1 和 mid2 要么都在 v 的左侧,要么分别在 v 的两侧。如下图所示。下一步,令 l=mid1 ,区间从 [l,r] 缩小为 [mid1,r] ,然后再继续把它分成 3 段。
(2)同理,若 f(mid1)>f(mid2),极值点 v 一定在 mid2 的左侧。如下图所示。下一步,令 r=mid2 ,区间从 [l,r] 缩小为 [l,mid2]。
不断缩小区间,就能使得区间 [l,r] 不断逼近 v ,从而得到近似值。
如何取 mid1 和 mid2 ?有两种基本方法:
(1)三等分:mid1 和 mid2 为 [l,r] 的三等分点。那么区间每次可以减少 1/3。
(2)近似三等分:计算 [l,r] 中间点 mid=(l+r)/2 ,然让 mid1 和 mid2 非常接近 mid ,例如 mid1=mid−eps,mid2=mid+eps,其中 eps 是一个很小的值。那么区间每次可以减少接近一半。
近似三等分比三等分要稍微快一点,不过在有些情况下这个 eps 过小可能导致这两种方法算出来的结果相等,导致判断错方向,所以其实不建议这么写。从复杂度上看,log3n 和 log2n 是差不多的。
提示:单峰函数的左右两边要严格单调,否则,可能在一边有 f(mid1)==f(mid2),导致无法判断如何缩小区间。
例题:
洛谷 P3382 【模板】三分法 :传送锚点
题目描述
如题,给出一个 N 次函数,保证在范围 [l,r] 内存在一点 x,使得 [l,x] 上单调增,[x,r] 上单调减。试求出 x 的值。
输入格式
第一行一次包含一个正整数 N 和两个实数 l,r,含义如题目描述所示。
第二行包含 N+1 个实数,从高到低依次表示该 N 次函数各项的系数。
输出格式
输出为一行,包含一个实数,即为 x 的值。若你的答案与标准答案的相对或绝对误差不超过 10−5 则算正确。
样例
输入数据1
3 -0.9981 0.5
1 -3 -3 1
样例说明
如图所示,红色段即为该函数 f(x)=x3−3x2−3x+1 在区间 [−0.9981,0.5] 上的图像。
当 x=−0.41421 时图像位于最高点,故此时函数在 [l,x] 上单调增,[x,r] 上单调减,故 x=−0.41421,输出 −0.41421。
数据规模与约定
对于 100% 的数据:6≤N≤13,函数系数均在 [−100,100] 内且至多 15 位小数,∣l∣,∣r∣≤10 且至多 15 位小数。l≤r。
模板题目
mid1 和 mid2 为 [l,r] 的三等分点
Code:
#include<bits/stdc++.h> using namespace std; using d=double; const d eps=1e-6; int n; d l,r; d a[15]; d f(d x)//计算函数值 { d s=0; for(int i=0;i<=n;i++) s=s*x+a[i]; return s; } int main() { cin>>n>>l>>r; for(int i=0;i<=n;i++) cin>>a[i]; while(r-l>eps) { d mid1=l+(r-l)/3; d mid2=r-(r-l)/3; if(f(mid1)>f(mid2)) r=mid2; else l=mid1; } cout<<fixed<<setprecision(5)<<l<<'\n'; }
洛谷 P3745 [六省联考 2017] 期末考试 :传送锚点
题目描述
有 n 位同学,每位同学都参加了全部的 m 门课程的期末考试,都在焦急的等待成绩的公布。
第 i 位同学希望在第 ti 天或之前得知所有课程的成绩。如果在第 ti 天,有至少一门课程的成绩没有公布,他就会等待最后公布成绩的课程公布成绩,每等待一天就会产生 C 不愉快度。
对于第 i 门课程,按照原本的计划,会在第 bi 天公布成绩。
有如下两种操作可以调整公布成绩的时间:
- 将负责课程 X 的部分老师调整到课程 Y,调整之后公布课程 X 成绩的时间推迟一天,公布课程 Y 成绩的时间提前一天;每次操作产生 A 不愉快度。
- 增加一部分老师负责学科 Z,这将导致学科 Z 的出成绩时间提前一天;每次操作产生 B 不愉快度。
上面两种操作中的参数 X,Y,Z 均可任意指定,每种操作均可以执行多次,每次执行时都可以重新指定参数。
现在希望你通过合理的操作,使得最后总的不愉快度之和最小,输出最小的不愉快度之和即可。
输入格式
第一行三个非负整数 A,B,C,描述三种不愉快度,详见【题目描述】;
第二行两个正整数 n,m,分别表示学生的数量和课程的数量;
第三行 n 个正整数 ti,表示每个学生希望的公布成绩的时间;
第四行 m 个正整数 bi,表示按照原本的计划,每门课程公布成绩的时间。
输出格式
输出一行一个整数,表示最小的不愉快度之和。
样例
输入数据 1
100 100 2
4 5
5 1 2 3
1 1 2 3 3
输出数据 1
6
输入数据 2
3 5 4
5 6
1 1 4 7 8
2 3 3 1 8 2
输出数据 2
33
数据范围
Case # | n,m,ti,bi | A,B,C |
---|---|---|
1, 2 | 1≤n,m,ti,bi≤2000 | A=109;B=109;0≤C≤102 |
3, 4 | 1≤n,m,ti,bi≤2000 | 0≤A;C≤102;B=109 |
5, 6, 7, 8 | 1≤n,m,ti,bi≤2000 | 0≤B≤A≤102;0≤C≤102 |
9 - 12 | 1≤n,m,ti,bi≤2000 | 0≤A,B,C≤102 |
13, 14 | 1≤n,m,ti,bi≤105 | 0≤A,B≤105;C=1016 |
15 - 20 | 1≤n,m,ti,bi≤105 | 0≤A,B,C≤105 |
【算法分析】
首先证明不愉快度是时间的下凹函数,那么就可以用三分法求极小值。
在如下参考代码代码中,第 30 行的 while 循环把极小值所在的区间缩小到了 r−l≤2,然后在第 37 行的 for 循环中求这个区间的极小值。
要注意特判 C 非常大时的情况。
整数三分的基本代码如下,注意第 1 行的 right-left>2 ,如果写成 right>left ,当 right-left<3 时会陷入死循环。
参考代码:
#include<bits/stdc++.h> const int N=100005; using namespace std; typedef long long ll; int n,m,t[N],b[N]; ll A,B,C,ans; ll calc1(int p) //计算通过A,B操作把时间调到p的不愉快度 { ll x=0,y=0; for(int i=1;i<=m;i++) { if(b[i]<p) x+=p-b[i]; else y+=b[i]-p; } if(A<B) return min(x,y)*A+(y-min(x,y))*B; //A<B,先用A填补,再用B else return y*B; //B<=A,直接全部使用B } ll calc2(int p) //计算学生们的不愉快度总和 { ll sum=0; for(int i=1;i<=n;i++) if(t[i]<p) sum+=(p-t[i])*C; return sum; } int main() { scanf("%lld %lld %lld %d %d",&A,&B,&C,&n,&m); for(int i=1;i<=n;i++) scanf("%lld",&t[i]); for(int i=1;i<=m;i++) scanf("%lld",&b[i]); sort(b+1,b+m+1); sort(t+1,t+n+1); if(C>=1e16) //一个特判 { cout<<calc1(t[1])<<endl; return 0; } ans=1e16; int l=1,r=N; //left, right while(r-l>2) //把2改成其他数字也行,后面的for再找最小值 { int mid1=l+(r-l)/3; int mid2=r-(r-l)/3; ll c1=calc1(mid1)+calc2(mid1); //总不愉快度 ll c2=calc1(mid2)+calc2(mid2); if(c1<=c2) r=mid2; else l=mid1; } for(int i=l;i<=r;i++) //在上面求出的区间内再枚举时间求出最小值 { ll x=calc1(i)+calc2(i); ans=min(ans,x); } cout<<ans<<endl; return 0; }
我的Code:
#include<bits/stdc++.h> using namespace std; using ll=long long; const int N=1e5+5; int n,m; int t[N],s[N]; ll a,b,c,ans; ll calc1(int x) { ll y=0,z=0; for(int i=1;i<=m;i++) if(s[i]<x) y+=x-s[i]; else z+=s[i]-x; if(a<b) return min(y,z)*a+(z-min(y,z))*b; else return z*b; } ll calc2(int x) { ll cnt=0; for(int i=1;i<=n;i++) if(t[i]<x) cnt+=(x-t[i])*c; return cnt; } int main() { cin>>a>>b>>c>>n>>m; for(int i=1;i<=n;i++) cin>>t[i]; for(int i=1;i<=m;i++) cin>>s[i]; sort(t+1,t+n+1); sort(s+1,s+m+1); if(c>=1e16) { cout<<calc1(t[1])<<'\n'; return 0; } ans=1e16; int l=1,r=N; while(r-l>2) { int mid1=l+(r-l)/3; int mid2=r-(r-l)/3; ll c1=calc1(mid1)+calc2(mid1); ll c2=calc1(mid2)+calc2(mid2); if(c1<=c2) r=mid2; else l=mid1; } for(int i=l;i<=r;i++) { ll x=calc1(i)+calc2(i); ans=min(ans,x); } cout<<ans<<'\n'; return 0; }
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现