信息学奥赛一本通Part1.2 基础算法-二分与三分

Posted cutele

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了信息学奥赛一本通Part1.2 基础算法-二分与三分相关的知识,希望对你有一定的参考价值。

问题 A: 【二分和三分】愤怒的牛

题目描述

农夫约翰建造了一座有n间牛舍的小屋,牛舍排在一条直线上,第i间牛舍在xi的位置,但是约翰的m头牛对小屋很不满意,因此经常互相攻击。约翰为了防止牛之间互相伤害,因此决定把每头牛都放在离其它牛尽可能远的牛舍。也就是要最大化最近的两头牛之间的距离。
牛们并不喜欢这种布局,而且几头牛放在一个隔间里,它们就要发生争斗。为了不让牛互相伤害。John决定自己给牛分配隔间,使任意两头牛之间的最小距离尽可能的大,那么,这个最大的最小距离是多少呢?

输入

第一行用空格分隔的两个整数n和m;
第二行为n个用空格隔开的整数,表示位置xi。

输出

输出仅一个整数,表示最大的最小距离值。

样例输入 Copy

5 3
1 2 8 4 9

样例输出 Copy

3

提示

把牛放在1,4,8这样最小距离是3

2≤n≤105 , 0≤xi≤109, 2≤m≤n
 
解题思路:二分答案即可,check当前距离所能放的牛数量。
技术图片
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+7;
int a[N],n,m;
bool check(int x){
    int t=a[0];
    int cnt=1;
    for(int i=1;i<n;i++)
        if(a[i]-t>=x){
            cnt++;
            t=a[i];
            if(cnt>=m) return true;
        }
    return false;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    sort(a,a+n);
    int l=0,r=a[n-1]-a[0];
    while(l<=r){
        int mid=l+r>>1;
        if(check(mid)) l=mid+1;
        else r=mid-1;
    }
    cout<<r<<endl;
    return 0;
}
View Code

问题 B: 【二分和三分】Best Cow Fences

题目描述

给定一个长度为n的正整数序列A,求一个平均数最大的,长度不小于L的子段。

输入

第一行用空格分隔的两个整数n和L;
第二行为n个用空格隔开的整数,表示Ai。

输出

输出一个整数,表示答案的1000倍。不用四舍五入,直接输出。

样例输入 Copy

10 6
6 4 2 10 3 8 5 9 4 1

样例输出 Copy

6500

提示

1≤n≤105 , 1≤Ai≤2000
 
解题思路:二分答案即可,为了方便计算可以先把数组中的元素*1000;在check时用到了前缀和;
技术图片
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
typedef long long ll;
using namespace std;
const int N=1e6+7;
ll a[N],n,m;
ll sum[N];//前缀和
bool check(ll x){
    for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i]-x;
    ll minn=inf;
    for(int i=m;i<=n;i++){
        minn=min(minn,sum[i-m]);
        if(sum[i]-minn>=0) return true;
    }
    return false;
}
int main(){
    scanf("%d%d",&n,&m);
    ll l,r;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        a[i]*=1000;
        l=min(l,a[i]);
        r=max(r,a[i]);
    }
    ll res;
    while(l<=r){
        ll mid=l+r>>1;
        if(check(mid)) res=mid,l=mid+1;
        else r=mid-1;
    }
    //res*=1000;
    cout<<res<<endl;
    return 0;
}
View Code

问题 C: 【二分和三分】曲线

题目描述

明明做作业的时候遇到了n个二次函数Si(x)=ax2+bx+c,他突发奇想设计了一个新的函数F(x)=max{Si(x)},i=1…n。
技术图片
明明现在想求这个函数在[0,1000]的最小值,要求精确到小数点后四位,四舍五入。

输入

输入包含T组数据,每组第一行一个整数n;
接下来n行,每行3个整数a,b,c,用来表示每个二次函数的3个系数。注意:二次函数有可能退化成一次。

输出

每组数据输出一行,表示新函数 F(x)的在区间 [0,1000]上的最小值。精确到小数点后四位,四舍五入。

样例输入 Copy

2
1
2 0 0
2
2 0 0
2 -4 2

样例输出 Copy

0.0000
0.5000

提示

对于50%的数据,1≤n≤100;
对于100%的数据,1≤T≤10,1≤n≤105,0≤a≤100,0≤∣b∣≤5000,0≤∣c∣≤5000。
 
解题思路:典型的三分,注意printf自带四舍五入!
技术图片
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
typedef long long ll;
using namespace std;
const int N=1e6+7;
int a[N],b[N],c[N],n;
//最小值
double check(double x){
    double minn=-inf;
    for(int i=1;i<=n;i++)
        minn=max(minn,a[i]*x*x+b[i]*x+c[i]);
    return minn;
}
int main(){
    int t;
    cin>>t;
    while(t--){
        cin>>n;
        for(int i=1;i<=n;i++) cin>>a[i]>>b[i]>>c[i];
        double l=0,r=1000;
        while(r-l>=1e-10){
            double mid1=l+(r-l)/3,mid2=r-(r-l)/3;
            if(check(mid1)>check(mid2)) l=mid1;
            else r=mid2;
        }
        printf("%.4lf
",check(l));
    }
    return 0;
}
View Code

问题 D: 【二分和三分】数列分段 II

题目描述

对于给定的一个长度为N的正整数数列A,现要将其分成M段,并要求每段连续,且每段和的最大值最小。
例如,将数列4 2 4 5 1要分成3段:
若分为[4 2][4 5][1],各段的和分别为6,9,1,和的最大值为9;
若分为[4][2 4] [5 1],各段的和分别为4,6,6,和的最大值为6;
并且无论如何分段,最大值不会小于6。
所以可以得到要将数列4 2 4 5 1要分成3段,每段和的最大值最小为6。

输入

第1行包含两个正整数N,M;
第2行包含N个空格隔开的非负整数Ai,含义如题目所述。

输出

仅包含一个正整数,即每段和最大值最小为多少。

样例输入 Copy

5 3
4 2 4 5 1

样例输出 Copy

6

提示

对于100%的数据,有N≤106,M≤N,Ai之和不超过109
解题思路:二分答案即可,注意边界。l=max(a[i])。当分成的段数最多时,每段和最大是数组中的最大值。当分成的段数最少时,为数组中全部元素的和
技术图片
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
typedef long long ll;
using namespace std;
const int N=1e6+7;
ll n,m,a[N];
//每段和最大值最小
bool check(ll x){
    ll sum=0,cnt=1;
    for(int i=1;i<=n;i++){
        if(sum+a[i]>x){
            sum=a[i];
            cnt++;
        }
        else sum+=a[i];
    }
    if(cnt<=m) return true;
    else return false;
}
int main(){
    scanf("%lld%lld",&n,&m);
    ll l=-inf,r=0;
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        l=max(l,a[i]);
        r+=a[i];
    }
    ll res;
    while(l<=r){
        ll mid=l+r>>1;
        if(check(mid)) res=mid,r=mid-1;
        else l=mid+1;
    }
    cout<<res<<endl;
    return 0;
}
View Code

问题 F: 【二分和三分】灯泡

题目描述

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

技术图片

输入

第一行包含一个整数T,表示测试数据的组数。
对于每组测试数据,仅一行,包含三个实数H,h和D,H表示灯泡的高度,h表示mildleopard的身高,D表示灯泡和墙的水平距离。

输出

共T行,每组数据占一行表示影子的最大长度,保留三位小数。

样例输入 Copy

3
2 1 0.5
2 0.5 3
4 3 4

样例输出 Copy

1.000
0.750
4.000

提示

T≤100,10−2≤H,h,D≤103,10−2≤H−h
 
解题思路:高中物理题+三分。注意一下有可能墙上没有影子,在计算影子长度时要判断是否小于0。也可以将左边界改为(H-h)*D/H。
技术图片
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
double H,h,D;
typedef long long ll;
double check(double x){
    if(-x-(H-h)*D/x+D+H < 0) return 0;
    return (-x-(H-h)*D/x+D+H);
}
int main(){
    int t;
 
    cin>>t;
    while(t--){
        cin>>H>>h>>D;
        double l=(H-h)*D/H,r=D;
        while(r-l>=1e-10){
            double mid1=l+(r-l)/3,mid2=r-(r-l)/3;
            if(check(mid1)>check(mid2)) r=mid2;
            else l=mid1;
        }
        printf("%.3lf
",check(l));
    }
    return 0;
}
View Code

问题 E: 【二分和三分】扩散



题目描述

一个点每过一个单位时间就会向4个方向扩散一个距离,如图所示:两个点a、b连通,记作e(a,b),当且仅当a、b的扩散区域有公共部分。连通块的定义是块内的任意两个点u、v都必定存在路径
e(u,a0),e(a0,a1),…e(ak,v)。
给定平面上的n个点,问最早什么时候它们形成一个连通块。

技术图片

输入

第一行一个数n,以下n行,每行一个点坐标。

输出

输出仅一个数,表示最早的时刻所有点形成连通块。

样例输入 Copy

2
0 0
5 5

样例输出 Copy

5

提示

对于100%的数据,满足1≤n≤50,1≤Xi,Yi≤109。
 解题思路:并查集+二分!能想到并查集的tql真的!也可以用最短路做,以后补吧
技术图片
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int N=55;
struct node{
    int x,y;
}a[N];
int d[N][N],fa[N],n;
int dis(node a,node b){
    return abs(a.x-b.x)+abs(a.y-b.y);
}
int ffind(int x){
    x==fa[x]?x:fa[x]=ffind(fa[x]);
}
void unionn(int x,int y){
    int fx=ffind(x),fy=ffind(y);
    if(fx!=fy) fa[fx]=fy;
}
//两个点垂直距离+水平距离<=2*t 这两个点就会相遇
bool check(int x){
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
            if(d[i][j]<=x*2)
                unionn(i,j);
    int sum=0;
    for(int i=1;i<=n;i++){
        if(fa[i]==i) sum++;
        if(sum==2) return false;
    }
    return true;
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i].x>>a[i].y;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            d[i][j]=dis(a[i],a[j]);
    int l=0,r=inf,ans;
    while(l<=r){
        int mid=l+r>>1;
        if(check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    cout<<ans<<endl;
    return 0;
}
View Code

问题 G: 【二分和三分】传送带


时间限制: 1 Sec  内存限制: 128 MB
[命题人:外部导入]

题目描述

在一个2维平面上有两条传送带,每一条传送带可以看成是一条线段。两条传送带分别为线段AB和线段CD。lxhgww在AB上的移动速度为P,在CD上的移动速度为Q,在平面上的移动速度R。现在lxhgww想从A点走到D点,他想知道最少需要走多长时间

输入

第一行是4个整数,表示A和B的坐标,分别为Ax,Ay,Bx,By
第二行是4个整数,表示C和D的坐标,分别为Cx,Cy,Dx,Dy
第三行是3个整数,分别是P,Q,R

输出

一行,表示lxhgww从A点走到D点的最短时间,保留到小数点后2位

样例输入 Copy

0 0 0 100
100 0 100 100
2 2 1

样例输出 Copy

136.60

提示

对于100%的数据,1<= Ax,Ay,Bx,By,Cx,Cy,Dx,Dy<=1000,1<=P,Q,R<=10
解题思路:三分套三分的毒瘤题
在线段AB和线段CD分别选一个点E,F。假设所需的最短时间的路径为:A->E->F->D。假设E点已经找到,然后线段CD上三分寻找F点的位置,使得E->F->D的时间最小。然后在线段AB上三分E点的位置,最后将两段的时间加起来就是最小的时间。
技术图片
#include<bits/stdc++.h>
using namespace std;
double xa,ya,xb,yb,xc,yc,xd,yd,p,q,r;
double dis(double x1,double y1,double x2,double y2){
    //return fabs(x1-x2)+fabs(y1-y2);
    return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
double check(double x,double y){
    double xl=xc,yl=yc,xr=xd,yr=yd;
    while(fabs(xr-xl)>1e-8||fabs(yr-yl)>1e-8){
        double xmid1=xl+(xr-xl)/3,ymid1=yl+(yr-yl)/3;
        double xmid2=xr-(xr-xl)/3,ymid2=yr-(yr-yl)/3;
        double t1=dis(xa,ya,x,y)/p+dis(x,y,xmid1,ymid1)/r+dis(xmid1,ymid1,xd,yd)/q;
        double t2=dis(xa,ya,x,y)/p+dis(x,y,xmid2,ymid2)/r+dis(xmid2,ymid2,xd,yd)/q;
        if(t1<t2) xr=xmid2,yr=ymid2;
        else xl=xmid1,yl=ymid1;
    }
    return dis(xa,ya,x,y)/p+dis(x,y,xl,yl)/r+dis(xl,yl,xd,yd)/q;
}
int main(){
    cin>>xa>>ya>>xb>>yb>>xc>>yc>>xd>>yd>>p>>q>>r;
    double xl=xa,yl=ya,xr=xb,yr=yb;
    while(fabs(xr-xl)>1e-8||fabs(yr-yl)>1e-8){
        double xmid1=xl+(xr-xl)/3,ymid1=yl+(yr-yl)/3;
        double xmid2=xr-(xr-xl)/3,ymid2=yr-(yr-yl)/3;
        if(check(xmid1,ymid1)<check(xmid2,ymid2)) xr=xmid2,yr=ymid2;
        else xl=xmid1,yl=ymid1;
    }
    printf("%.2lf",check(xl,yl));
    return 0;
}
View Code

 

以上是关于信息学奥赛一本通Part1.2 基础算法-二分与三分的主要内容,如果未能解决你的问题,请参考以下文章

信息学奥赛一本通要多少钱

信息学奥赛一本通为啥不通过

长春市哪里有卖这本信息学奥赛一本通c++的书店?

信息奥赛一本通 1060:均值

《信息学奥赛一本通》题库 1034 计算三角形面积——基础

信息学奥赛一本通Part6.1 数学基础-快速幂