信息学奥赛一本通Part1.2 基础算法-二分与三分
Posted cutele
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了信息学奥赛一本通Part1.2 基础算法-二分与三分相关的知识,希望对你有一定的参考价值。
问题 A: 【二分和三分】愤怒的牛
题目描述
农夫约翰建造了一座有n间牛舍的小屋,牛舍排在一条直线上,第i间牛舍在xi的位置,但是约翰的m头牛对小屋很不满意,因此经常互相攻击。约翰为了防止牛之间互相伤害,因此决定把每头牛都放在离其它牛尽可能远的牛舍。也就是要最大化最近的两头牛之间的距离。
牛们并不喜欢这种布局,而且几头牛放在一个隔间里,它们就要发生争斗。为了不让牛互相伤害。John决定自己给牛分配隔间,使任意两头牛之间的最小距离尽可能的大,那么,这个最大的最小距离是多少呢?
输入
第一行用空格分隔的两个整数n和m;
第二行为n个用空格隔开的整数,表示位置xi。
第二行为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
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。
第二行为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个系数。注意:二次函数有可能退化成一次。
接下来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。
对于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; }
问题 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,含义如题目所述。
第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表示灯泡和墙的水平距离。
对于每组测试数据,仅一行,包含三个实数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; }
问题 E: 【二分和三分】扩散
解题思路:并查集+二分!能想到并查集的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
第二行是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; }
以上是关于信息学奥赛一本通Part1.2 基础算法-二分与三分的主要内容,如果未能解决你的问题,请参考以下文章