A- 查找
题目大意:输入 n 个不超过 10^9 的单调不减的(就是后面的数字不小于前面的数字)非负整数 a1,a2,…,an,然后进行 m 次询问。对于每次询问,给出一个整数 q,要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 −1 。
解题思路:由于本题输出量较大,需要使用较快的二分算法。#include<stdio.h> int a[1000010]; int m,n; void work(int b) { int l=0; int r=n-1; while(l<r) { int mid=(l+r)/2; if(a[mid]>=b) { r=mid; } else { l=mid+1; } } if(b==a[l]) { printf("%d ",l+1); } else { printf("-1 "); } } int main() { int i,b; scanf("%d %d",&n,&m); for(i=0;i<n;i++) { scanf("%d",&a[i]); } for(i=0;i<m;i++) { scanf("%d",&b); work(b); } return 0; }
B- A-B数对
题目大意:给出一串正整数数列以及一个正整数 C,要求计算出所有满足 A−B=C 的数对的个数。
解题思路:由于本题的n小于2e5,数据量较大,需要通过二分查找简化算法,通过遍历由第一个a[i]为A,算出B,通过二分查找找到对应的B的最左端和最右端,得到B的个数。#include<stdio.h> #include<algorithm> using namespace std; long long a[200010]; long long n; long long work(long long b) { long long l1=0,r1=n-1,mid1; while(l1<r1) { mid1=l1+r1>>1; if(a[mid1]>=b) { r1=mid1; } else { l1=mid1+1; } } if(a[l1]!=b) return 0; long long l2=l1-1,r2=n-1,mid2; while(l2<r2) { mid2=l2+r2+1>>1; if(a[mid2]<=b) { l2=mid2; } else { r2=mid2-1; } } return l2-l1+1; } int main() { long long c,sum=0; int i; scanf("%lld %lld",&n,&c); for(i=0;i<n;i++) { scanf("%lld",&a[i]); } sort(a,a+n); for(i=0;i<n;i++) { if(a[i]>c) { sum+=work(a[i]-c); } } printf("%lld",sum); return 0; }
C- 分巧克力
题目大意:小明一共有 N 块巧克力,其中第 i 块是 Hi×Wi 的方格组成的长方形。小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。切出的巧克力需要满足:1. 形状是正方形,边长是整数。2. 大小相同。
解题思路:在输入的时候找到面积最大的一块巧克力记为max,以最大的这块巧克力的边长作为right,以0作为left,根据二分一步一步缩小范围,若根据mid分出来的巧克力块数多了说明mid小了,巧克力块数少了说明mid大了。#include<stdio.h> int n,k; struct move { int start,end; }a[100010]; int check(int mid) { int i,sum=0; for(i=0;i<n;i++) { sum+=(a[i].end /mid)*(a[i].start /mid); } return sum; } int work(int l,int r) { while(l<r) { int mid=l+r+1>>1; if(check(mid)>=k) { l=mid; } else { r=mid-1; } } return l; } int main() { int maxs=0,maxe=0,i,j; scanf("%d %d",&n,&k); for(i=0;i<n;i++) { scanf("%d %d",&a[i].start ,&a[i].end ); if(maxs<a[i].start ) { maxs=a[i].start ; } if(maxe<a[i].end ) { maxe=a[i].end ; } } int l=1,r; if(maxs>maxe) { r=maxs; } else { r=maxe; } printf("%d",work(l,r)); return 0; }
D- 跳石头
题目大意:组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能移走起点和终点的岩石)。
解题思路:以终点和起点之间的距离为right,以0为left,根据二分一步一步缩小范围,在check函数中若搬走的石头大于题目中可以搬走的石头数量,则说明mid大了,若搬走的石头数量小了,说明mid小了。#include<stdio.h> int a[50001]; int l,n,m; int tiao(int left,int right) { if(right-1==left) return left; int mid=(left+right)/2; int num=0; int flag=0; int k=0;//k记录当前石头的前一块石头距离岸边的距离 for(int i=1;i<=n+1;i++) { int dis=a[i]-k; if(dis>=mid)//如果长度小于等于mid,距离合适,不搬石头 { k=a[i]; } else//石头要被移走 { num++; if(num>m) { flag=1; break; } } } if(flag) { return tiao(left,mid); } return tiao(mid,right); } int main() { int i; scanf("%d %d %d",&l,&n,&m); a[0]=0; for(i=1;i<=n;i++) { scanf("%d",&a[i]); } a[n+1]=l; printf("%d",tiao(1,l+1));//表示[left,right)找到最短距离最大的区间 return 0; }
E- Cable master
题目大意:光头强需要对木头进行切割,并把长度相同的K根木头送到公司(本题当中的给出的木头长度单位是米,光头强的切割技术只能精确到厘米,也就是说答案保留到小数点后两位小数)。
解题思路:这道题是有小数的二分,首先得到最大的木头的长度作为right,以0作为left,得到的mid通过check函数检查,若切的块数大于李老板要求的块数,则说明mid设小了,若切的块数小于李老板要求的块数,说明mid大了。这道题我wa了特别多次,一直不懂为什么,明明答案是对的,但是一直wa,最后没办法找了同学帮忙看,才知道是被卡精度了,但是还是觉得离谱,我的精度已经卡到1e-7了还是过不了,明明题目只要求保留两位小数,最后知道了这种题直接给它循环来个一百次就好了。#include<stdio.h> #include<math.h> double a[10010]; double n,k; int check(double mid) { int sum=0,i; for(i=0;i<n;i++) { sum+=(int)(a[i]/mid); } return sum; } void work(double l,double r) { int i; for(i=0;i<100;i++) { double mid=(l+r)/2; if(check(mid)>=k)//分的木头多了,要增大单块木头的长度 { l=mid; } else { r=mid; } } printf("%.2f\n",floor(l*100)/100); } int main() { int i; double max=0,sum=0; scanf("%lf %lf",&n,&k); for(i=0;i<n;i++) { scanf("%lf",&a[i]); sum+=a[i]; if(max<a[i]) { max=a[i]; } } sum/=n; double l=0,r; if(sum>max) { r=sum; } else { r=max; } work(l,r); return 0; }
F- Yukari’s Birthday
题目大意:给蛋糕一圈一圈插上蜡烛,第i圈为k的i次方,相当于再求一个关于k的i次方的等比数列的前n项和。
解题思路:枚举+二分。枚举r,然后再二分得出k的大小。在做这题的时候一直wa是因为没有考虑到乘法溢出,需要判断乘法会不会溢出。#include<stdio.h> #include<math.h> int n; int check(int mid,int r) { int sum=0,i; for(i=1;i<=r;i++) { sum+=pow(mid,i); } return sum; } int work(int r) { int right=0; int left=n+1; while(right!=left+1) { int mid=left+right>>1; if(check(mid,r)>=n) { right=mid; } else{ left=mid; } } return left; } int main() { int r,k; int min=0x3f3f3f3f; int minr=0x3f3f3f3f; int mink=0x3f3f3f3f; while(~scanf("%d",&n)) { for(r=1;r<=100;r++) { k=work(r); if(min>k*r) { mink=k; minr=r; min=k*r; } else if(min=k*r) { if(minr>r) { minr=r; mink=k; } } } printf("%d %d",minr,mink); } return 0; }
G- Pie
题目大意:有n个不同大小的蛋糕,要求分成k份,所有人拿到的蛋糕是同样大小的 (但不必是同样形状的)
解题思路:在输入的时候输入的是半径,我们要把它处理成半径的平方,然后以最大的那块蛋糕作为right,以0作为left,在check函数中,若得到的蛋糕块数大于总人数说明mid小了,若得到的蛋糕块数小于总人数则说明mid大了。#include<stdio.h> #include<math.h> #include<string.h> int a[10010]; int f,n; const double pi=acos(-1.0); int check(double mid) { int sum=0,i; for(i=0;i<n;i++) { sum+=(int)(a[i]/mid); } return sum; } void work(double l,double r) { double mid; while(r-l>1e-7) { mid=(r+l)/2; if(check(mid)>=f+1)//蛋糕块数多了 { l=mid; } else//蛋糕块数少了 { r=mid; } } printf("%.4lf\n",mid*pi); } int main() { int t,i,j; double max; scanf("%d",&t); while(t--) { memset(a,0,sizeof(a)); max=0; scanf("%d %d",&n,&f); for(i=0;i<n;i++) { scanf("%d",&a[i]); a[i]*=a[i]; if(max<a[i])//取pie的最大值 { max=a[i]; } } double l=0,r=max; work(l,r); } return 0; }
H- Monthly Expense
题目大意:给你一个长度为N的序列,现在需要把他们切割成M个子序列(所以每一份都是连续的),使得每个子序列和均不超过某个值X。
解题思路:累加得到这个序列的总数,以这个序列的总数作为right,以0作为left,通过check函数若分成的段数大于题目要求的段数说明mid小了,分的段数小于题目要求说明mid大了。#include<stdio.h> int a[100010]; int m,n; int check(long long mid) { int i,j,count=0; long long sum=0; for(i=0;i<n;i++) { if(sum+a[i]>mid) { if(i!=n-1) { sum=0; i--; } count++; } else { sum+=a[i]; } } if(!sum)count++; return count; } void work(long long l,long long r) { int i; while(l<r) { long long mid=r+l>>1; if(check(mid)>=m)//说明选的数字小了 { l=mid+1; } else { r=mid; } } printf("%lld\n",l); } int main() { int i; while(~scanf("%d %d",&n,&m)) { long long sum=0,max=0; for(i=0;i<n;i++) { scanf("%d",&a[i]); sum+=a[i]; if(max<a[i]) { max=a[i]; } } long long l=max,r=sum; work(l,r); } return 0; }
I- 三分法
题目大意:给出一个 N 次函数,保证在范围 [l, r] 内存在一点 x,使得 [l, x] 上单调增,[x, r] 上单调减。试求出 x 的值。
解题思路:该题求得是最大值,需要将区间分成三段:[left,mid1],[mid1,mid2],[mid2,right]。mid1=(left+right)/2,mid2=(right+mid1)/2。然后算的时候看是mid1对应的函数值大,还是mid2对应的函数值大,如果是mid1的函数值大,那就right=mid2,如果是mid2的函数值大,那就left=mid1。#include<stdio.h> #include<math.h> int n; double a[20]; double check(double mid) { double sum=0; int i; for(i=0;i<=n;i++) { sum+=a[i]*pow(mid,n-i); } return sum; } void work(double l,double r) { while(r-l>1e-8) { double mid1=(l+r)/2; double mid2=(mid1+r)/2; if(check(mid1)>check(mid2)) { r=mid2; } else { l=mid1; } } printf("%.5lf",l); } int main() { int i; double l,r; scanf("%d %lf %lf",&n,&l,&r); for(i=0;i<=n;i++) { scanf("%lf",&a[i]); } work(l,r); return 0; }
J- Freefall
题目大意:
高桥的行星有一个常数 g 表示重力的强度,他开始下落后到达地面的时间是A/sqrt(g)。现在时间是0,g = 1。高桥将执行以下操作多次,只要他想(可能零)。使用超能力将 g 的值增加1。这需要一个 B 的时间,然后,他会跳楼。在开始下降后,他不能改变 g 的值,此外,我们只考虑执行操作和下降所需的时间。找到高桥能到达地面的最早时间。
解题思路:这道题相当于构造一个函数为y=b*x+a/sqrt(x+1)利用三分查找(整数)找到答案,由于本题数据较大,最大的需要注意的点是要看好数据范围,所有数据不是long long就是double,我因为数据范围wa了两次。#include<stdio.h> #include<math.h> #include<algorithm> using namespace std; long long a,b; double check(double m) { double sum; sum=1.0*m*b+1.0*a/sqrt(m+1.0); return sum; } void work(long long l,long long r) { long long i; while(r-l>2) { long long mid1=(l+r)/2; long long mid2=(mid1+r)/2; if(check(mid1)>check(mid2)) { l=mid1; } else { r=mid2; } } double sum=1e18; for(i=l;i<=r;i++)sum=min(sum,check(i)); printf("%0.8f\n",sum); } int main() { long long left,right; scanf("%lld %lld",&a,&b); left=0; right=1e18; work(left,right); return 0; }
K- Last Rook
题目大意:二维棋盘,现在,N-1车被放置在棋盘上,以便所有上述条件都得到满足。你要选择一个没有车的广场,并在那个广场上放一辆车。(可以证明,在这种情况下,至少有一个正方形上可以放置车。)但是,你不能直接看到棋盘的哪些方格被车占据。相反,你最多可以用以下方式向法官提出20个问题。
解题思路:通过横向二分缩小区间,二维变一维了,在横向二分缩小区间,这道题对我来说最最最大的困难就是读不懂英文。#include<iostream> using namespace std; int main() { int n,l=1,r,x1,y1,x; cin>>n; r=n; while(l<r) { int mid=(l+r)/2; cout<<"? "<<l<<" "<<mid<<" "<<"1 "<<n<<endl; cin>>x; if(x==mid-l+1)//说明mid往前到l这段都有赛车 { l=mid+1; } else { r=mid; } } x1=l; l=1; r=n; while(l<r) { int mid=(l+r)/2; cout<<"? "<<"1 "<<n<<" "<<l<<" "<<mid<<endl; cin>>x; if(x==mid-l+1) { l=mid+1; } else { r=mid; } } cout<<"! "<<x1<<" "<<r<<endl; return 0; }