返回顶部

三分答案

三分答案入门

三分法求单峰(或者单谷)的极值,是二分法的一个简单扩展。

单峰函数和单谷函数如下图,函数 f(x)f(x) 在区间 [l,r][l,r] 内,只有一个极值 vv ,在极值点两边,函数是单调变化的。以单峰函数为例,在 vv 的左边,函数是严格单调递增的,在 vv 右边是严格单调递减的。

                                                                  图1 (1)单峰函数                                                                                                                                                             (2)单谷函数


下面的讲解都以求单峰极值为例。

如何求单峰函数最大值的近似值?虽然不能直接用二分法,不过,只要稍微变形一下,就能用了。

在 [l,r][l,r] 上任取 22 个点,mid_1mid1 和 mid_2mid2 ,把函数分成 33 段。有以下情况:

(1)若 f(mid_1)f(mid1) < f(mid_2)f(mid2),极值点 vv 一定在 mid_1mid1 的右侧。此时,mid_1mid1 和 mid_2mid2 要么都在 vv 的左侧,要么分别在 vv 的两侧。如下图所示。下一步,令 l=mid_1l=mid1 ,区间从 [l,r][l,r] 缩小为 [mid_1,r][mid1,r] ,然后再继续把它分成 33 段。

图2 情况(1):极值点 vv 在 mid_1mid1 右侧

(2)同理,若 f(mid_1)>f(mid_2)f(mid1)>f(mid2),极值点 vv 一定在 mid_2mid2 的左侧。如下图所示。下一步,令 r=mid_2r=mid2 ,区间从 [l,r][l,r] 缩小为 [l,mid_2][l,mid2]

图3 情况(2):极值点 vv 在 mid_1mid1 右侧

不断缩小区间,就能使得区间 [l,r][l,r] 不断逼近 vv ,从而得到近似值。

如何取 mid_1mid1 和 mid_2mid2 ?有两种基本方法:

(1)三等分mid_1mid1 和 mid_2mid2 为 [l,r][l,r] 的三等分点。那么区间每次可以减少 1/31/3

(2)近似三等分:计算 [l,r][l,r] 中间点 mid=(l+r)/2mid=(l+r)/2 ,然让 mid_1mid1 和 mid_2mid2 非常接近 midmid ,例如 mid_1=mid-epsmid1=midepsmid_2=mid+epsmid2=mid+eps,其中 epseps 是一个很小的值。那么区间每次可以减少接近一半。

近似三等分比三等分要稍微快一点,不过在有些情况下这个 epseps 过小可能导致这两种方法算出来的结果相等,导致判断错方向,所以其实不建议这么写。从复杂度上看,log_3nlog3n 和 log_2nlog2n 是差不多的。

提示:单峰函数的左右两边要严格单调,否则,可能在一边有 f(mid_1)==f(mid_2)f(mid1)==f(mid2),导致无法判断如何缩小区间。

例题:

洛谷 P3382 【模板】三分法 :传送锚点

 

题目描述

如题,给出一个 NN 次函数,保证在范围 [l, r][l,r] 内存在一点 xx,使得 [l, x][l,x] 上单调增,[x, r][x,r] 上单调减。试求出 xx 的值。

输入格式

第一行一次包含一个正整数 NN 和两个实数 l, rl,r,含义如题目描述所示。

第二行包含 N + 1N+1 个实数,从高到低依次表示该 NN 次函数各项的系数。

输出格式

输出为一行,包含一个实数,即为 xx 的值。若你的答案与标准答案的相对或绝对误差不超过 10^{-5}105 则算正确。

样例

输入数据1

3 -0.9981 0.5

 

1 -3 -3 1

输出数据1
-0.41421

样例说明

 

 

 

如图所示,红色段即为该函数 f(x) = x^3 - 3 x^2 - 3x + 1f(x)=x33x23x+1 在区间 [-0.9981, 0.5][0.9981,0.5] 上的图像。

当 x = -0.41421x=0.41421 时图像位于最高点,故此时函数在 [l, x][l,x] 上单调增,[x, r][x,r] 上单调减,故 x = -0.41421x=0.41421,输出 -0.414210.41421

数据规模与约定

对于 100\%100% 的数据:6 \le N \le 136N13,函数系数均在 [-100,100][100,100] 内且至多 1515 位小数,|l|,|r|\leq 10l,r10 且至多 1515 位小数。l\leq rlr

模板题目

mid1 和 mid_2mid2 为 [l, r][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] 期末考试传送锚点

 

题目描述

有 nn 位同学,每位同学都参加了全部的 mm 门课程的期末考试,都在焦急的等待成绩的公布。

第 ii 位同学希望在第 t_iti 天或之前得知所有课程的成绩。如果在第 t_iti 天,有至少一门课程的成绩没有公布,他就会等待最后公布成绩的课程公布成绩,每等待一天就会产生 CC 不愉快度。

对于第 ii 门课程,按照原本的计划,会在第 b_ibi 天公布成绩。

有如下两种操作可以调整公布成绩的时间:

  1. 将负责课程 XX 的部分老师调整到课程 YY,调整之后公布课程 XX 成绩的时间推迟一天,公布课程 YY 成绩的时间提前一天;每次操作产生 AA 不愉快度。
  2. 增加一部分老师负责学科 ZZ,这将导致学科 ZZ 的出成绩时间提前一天;每次操作产生 BB 不愉快度。

上面两种操作中的参数 X, Y, ZX,Y,Z 均可任意指定,每种操作均可以执行多次,每次执行时都可以重新指定参数。

现在希望你通过合理的操作,使得最后总的不愉快度之和最小,输出最小的不愉快度之和即可。

输入格式

第一行三个非负整数 A, B, CA,B,C,描述三种不愉快度,详见【题目描述】;

第二行两个正整数 n, mn,m,分别表示学生的数量和课程的数量;

第三行 nn 个正整数 t_iti,表示每个学生希望的公布成绩的时间;

第四行 mm 个正整数 b_ibi,表示按照原本的计划,每门课程公布成绩的时间。

输出格式

输出一行一个整数,表示最小的不愉快度之和。

样例

输入数据 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, t_i, b_in,m,ti,biA, B, CA,B,C
1, 2 1 \leq n, m, t_i, b_i \leq 20001n,m,ti,bi2000 A = 10^9; B = 10^9; 0 \leq C \leq 10^2A=109;B=109;0C102
3, 4 1 \leq n, m, t_i, b_i \leq 20001n,m,ti,bi2000 0 \leq A; C \leq 10^2; B = 10^90A;C102;B=109
5, 6, 7, 8 1 \leq n, m, t_i, b_i \leq 20001n,m,ti,bi2000 0 \leq B \leq A \leq 10^2; 0 \leq C \leq 10^20BA102;0C102
9 - 12 1 \leq n, m, t_i, b_i \leq 20001n,m,ti,bi2000 0 \leq A, B, C \leq 10^20A,B,C102
13, 14 1 \leq n, m, t_i, b_i \leq 10^51n,m,ti,bi105 0 \leq A, B \leq 10^5; C = 10^{16}0A,B105;C=1016
15 - 20 1 \leq n, m, t_i, b_i \leq 10^51n,m,ti,bi105 0 \leq A, B, C \leq 10^50A,B,C105

 

【算法分析】

首先证明不愉快度是时间的下凹函数,那么就可以用三分法求极小值。

在如下参考代码代码中,第 3030 行的 whilewhile 循环把极小值所在的区间缩小到了 r-l≤2rl2,然后在第 3737 行的 forfor 循环中求这个区间的极小值。

要注意特判 CC 非常大时的情况。

整数三分的基本代码如下,注意第 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;
}
复制代码


如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
posted @   光暗之影x  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示