【信息学奥赛一本通】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;
}

问题 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;
}

问题 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\n",check(l));
    }
    return 0;
}

问题 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;
}

问题 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\n",check(l));
    }
    return 0;
}

问题 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;
}

问题 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;
}

原文地址:https://www.cnblogs.com/Cutele/p/12242983.html

时间: 2024-08-30 06:44:00

【信息学奥赛一本通】Part1.2 基础算法-二分与三分的相关文章

【信息学奥赛一本通 提高组】第二章 二分与三分

一.二分 二分法,在一个单调有序的集合或函数中查找一个解,每次分为左右两部分,判断解在那个部分并调整上下界,直到找到目标元素,每次二分都将舍弃一般的查找空间,因此效率很高. 二分常见模型 1.二分答案 最小值最大(或是最大值最小)问题,这类双最值问题常常选用二分法求解,也就是确定答案后,配合贪心,DP等其他算法检验这个答案是否合理,将最优化问题转化为判定性问题.例如,将长度为n的序列ai分为最多m个连续段,求所有分法中每段和的最大值的最小是多少? 2.二分查找 用具有单调性的布尔表达式求解分界点

最大子矩阵(信息学奥赛一本通 1224)

[题目描述] 已知矩阵的大小定义为矩阵中所有元素的和.给定一个矩阵,你的任务是找到最大的非空(大小至少是1×1)子矩阵. [输入] 输入是一个N×N的矩阵.输入的第一行给出N(0<N≤100).再后面的若干行中,依次(首先从左到右给出第一行的N个整数,再从左到右给出第二行的N个整数……)给出矩阵中的N2个整数,整数之间由空白字符分隔(空格或者空行).已知矩阵中整数的范围都在[−127,127]. [输出] 输出最大子矩阵的大小. [输入样例] 4 0 -2 -7 0 9 2 -6 2 -4 1

【信息学奥赛一本通】第三部分_栈 ex1_4cale (中缀转后缀7符号)

其实这个中缀转后缀是费了很大功夫的,明白算法后第一次实现花了近三小时ORZ #include <stdio.h> #include <string.h> #include <ctype.h> char Mstr[511],Msta[511] = {'@'},Bstr[511]; int sta[511]; const short list[4][4] = {{0,-1,1,1},{1,0,1,1},{1,1,1,-2},{-1,-1,2,1}}; int level (

信息学奥赛一本通 5.1 区间类动态规划

石子合并[loj 10147] /* dp[i][j]=max or min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]) i<=k<j */ #include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; inline int re

信息学奥赛一本通 5.4 状态压缩动态规划

#loj 10170. 「一本通 5.4 例 1」骑士 看数据范围n<=10,所以不是搜索就是状压dp,又因为搜索会超时所以用dp dp[i][k][j]表示现已经放到第i行,前面共有k个,这一行状态为j so,dp[i][k][j]=dp[i-1][k-num[j]][t] #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cma

基础算法——二分

上次我们讲了贪心,上题: 题目描述: RFdragon摘了n种果子,每种果子堆成一堆,现在RFdragon打算将所有果子运回家,因此决定将所有果子合并成一堆.每次RFdragon可以选择任意两堆果子进行合并,消耗等同于两堆果子质量之和的力气.请你求出一个合并方案,使得RFdragon消耗的力气最少. 输入描述: 第一行一个正整数n. 第二行n个正整数,表示每堆果子的质量. 输出描述: 一个正整数,表示消耗力气的最小值. 输入样例: 5 3 2 5 2 1 输出样例: 29 其他说明: n<100

信息学奥赛一本通 提高篇 序列第k个数 及 快速幂

我是传送门 这个题首先是先判断是等差还是等比数列 等差的话非常简单: 前后两个数是等差的,举个栗子: 3 6 9 12 这几个数,(我感觉 1 2 3 4并说明不了什么) 每次都加3嘛,很容易看出,第一个数是3 * 1,第二个是3 * 2....以此类推 第k个数 = (第2个数 - 第1个数) * k ; (z - y) * k % 200907 % 200907 的原因是题目要求 但是这样并不能过 hack一下 4 7 10 13 用原先的公式:(7 - 4) * 4 % 200907 =

最大公约数(信息学奥赛一本通 1627)

[题目描述] 给出两个正整数 A,B,求它们的最大公约数. [输入] 输入共两行,第一行一个正整数 A,第二行一个正整数 B. [输出] 在第一行输出一个整数,表示 A,B 的最大公约数. [输入样例] 18 24 [输出样例] 6 [提示] 数据范围与提示: 对于 60% 的数据,1≤A,B≤1018: 对于 100% 的数据,1≤A,B≤103000 . 首先看到这道题的数据范围之后,我们肯定是得用高精度呐,然后为了方便一点,缩小数组大小,我们还可以引进“压位高精度”,一般来说,最好使用四位

【信息学奥赛一本通】第三部分_队列 ex2_3produce 产生数

给出一个整数n(n<=2000)(代码可适用n<=10^31)和k个变换规则(k<=15). 规则:1.1个数字可以变换成另1个数字: 2.规则中右边的数字不能为零. BFS 1 #include <stdio.h> 2 #include <string.h> 3 #define maxn 1000 4 5 char num[33]; 6 int len,q[maxn],Visited[11]; 7 long long ans = 1; 8 9 int main