洛谷题单:【算法1-6】二分查找与二分答案

(1)P2249 【深基13.例1】查找
这道题本身并不难,典型的一个二分查找,用java自带的二分查找函数就可以实现.但是这题存在多个值相同的情况,根据例题我们知道,返回的是第一个数字。但如果直接用二分搜索肯定不能保障搜索到的是第一个,那么 我们就要自己手写一个二分查找,并且当找到了元素后再判断一下是不是第一个,如果不是我们就往前移。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;

public class Main {
	public static void main(String[] args) throws IOException {
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		PrintWriter out=new PrintWriter(System.out);
		String[] s=br.readLine().split(" ");
		int n=Integer.valueOf(s[0]),m=Integer.valueOf(s[1]);
		int[] arr=new int[n+1];
		s=br.readLine().split(" ");
		for(int i=1;i<=n;i++) {
			arr[i]=Integer.valueOf(s[i-1]);
		}
		s=br.readLine().split(" ");
		for(int i=0;i<m;i++) {
			out.print(binarySearch(1, n, arr, Integer.valueOf(s[i]))+" ");
		}
		out.close();
	}
	static int binarySearch(int l,int r,int[] arr,int k) {
		int res=-1;
		while(l<=r) {
			int m=(l+r)/2;
			if(arr[m]==k) {
				res=m;
				break;
			}
			else if(arr[m]>k) {
				r=m-1;
			}
			else if(arr[m]<k) {
				l=m+1;
			}
		}
		if(res!=-1) {
			while(arr[res-1]==k&&res!=1) {
				res--;
			}
		}
		return res;
	}
}

(2)P1102 A-B 数对
这道题有一个非常巧妙的方法,利用Map集合,首先遍历一遍数组,将各个数字和它出现的次数用map存储起来。然后将数组中每个元素减c。最后再遍历一遍数组,把数字中每个元素出现的次数累加起来。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;

public class Main {
	public static void main(String[] args) throws IOException {
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		String[] s=br.readLine().split(" ");
		int n=Integer.valueOf(s[0]),m=Integer.valueOf(s[1]);
		HashMap<Integer, Integer> hs=new HashMap<Integer, Integer>();
		int[] arr=new int[n];
		s=br.readLine().split(" ");
		for(int i=0;i<n;i++) {
			arr[i]=Integer.valueOf(s[i]);
			if(hs.containsKey(arr[i])) {
				hs.put(arr[i], hs.get(arr[i])+1);
			}
			else {
				hs.put(arr[i], 1);
			}
			arr[i]-=m;
		}
		long sum=0;
		for(int i=0;i<n;i++) {
			if(hs.containsKey(arr[i]))
				sum+=hs.get(arr[i]);
		}
		System.out.println(sum);
	}
}

(3)P1873 砍树
一道二分答案题目,像这种题目一般都是有一个模板的。用二分来选取一个砍树的高度,然后写个函数判断这个长度是否满足条件,如果满足,则在更高的长度进行选取,不满足则在低的长度选取。
ps:这题有一个点用java做会超内存。。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
	static int[] arr;
	public static void main(String[] args) throws IOException {
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		String[] s=br.readLine().split(" ");
		int n=Integer.valueOf(s[0]),m=Integer.valueOf(s[1]);
		arr=new int[n];
		s=br.readLine().split(" ");
		for(int i=0;i<n;i++) {
			arr[i]=Integer.valueOf(s[i]);
		}
		int l=1,r=1000000000;
		int max=0;
		while(l<=r) {
			int mid=(l+r)/2;
			if(judge(mid,m)) {
				max=mid;
				l=mid+1;
			}
			else {
				r=mid-1;
			}
		}
		System.out.println(max);
	}
	public static boolean judge(int mid,int k) {
		int sum=0;
		for(int i=0;i<arr.length;i++) {
			if(arr[i]>mid) {
				sum+=arr[i]-mid;
			}
			if(sum>=k) {
				return true;
			}
		}
		return false;
	}
}

(4)P1024 一元三次方程求解
首先,解的范围是-100到100,而且每个解绝对值差大于1,那么可以枚举每个区间,每个区间的大小为1。然后把区间端点带入方程,如果左端点为0,直接输出左端点。否则用二分的思想,在区间内寻找解,因为要满足f(x1)*f(x2)<0才可能有解

import java.util.Scanner;

public class Main {
	static double a,b,c,d;
	public static double f(double x) {
		return a*x*x*x+b*x*x+c*x+d;
	}
	public static void main(String[] args) {
		Scanner sc=new Scanner(System.in);
		a=sc.nextDouble();b=sc.nextDouble();c=sc.nextDouble();d=sc.nextDouble();
		double l,r;
		for(int i=-100;i<100;i++) {
			l=i;r=i+1;
			double f1=f(l);
			double f2=f(r);
			if(f1==0) 
	        {
				System.out.print(String.format("%.2f", l)+" ");
	        }      
			if(f1*f2<0) {
				while(r-l>=0.001) {
					double m=(l+r)/2;
					if(f(m)*f(r)<=0) 
		                   l=m; 
		                else 
		                   r=m; 
				}
				System.out.print(String.format("%.2f", r)+" ");
			}
		}
	}
}

(5)P1678 烦恼的高考志愿
一道二分搜索的题目。首先把学校分数线进行排序,然后遍历学生的分数,对每一个学生分数,二分的去学校分数线找最接近学生分数的分数线。然后对这个分数线左边右边和本身这三个数取一个最小值,这个最小值就是估分的误差。

import java.util.Arrays;
import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner sc=new Scanner(System.in);
		int m=sc.nextInt(),n=sc.nextInt();
		int[] arr=new int[m];
		for(int i=0;i<m;i++) {
			arr[i]=sc.nextInt();
		}
		Arrays.sort(arr);
		long sum=0;
	    int ans,l,r,mid=0,res=Integer.MAX_VALUE;
		for(int z=0;z<n;z++) {
			ans=sc.nextInt();
			l=0;r=m-1;
			while(l<=r) {
				mid=(l+r)/2;
				if(ans==arr[mid]) {
					break;
				}
				else if(arr[mid]<ans){
					l=mid+1;
				}
				else {
					r=mid-1;
				}
			}
			res=Math.abs(arr[mid]-ans);
			if(mid!=0) 	res=Math.min(res, Math.abs(arr[mid-1]-ans));
			if(mid!=m-1) res=Math.min(res,Math.abs(arr[mid+1]-ans));
			sum+=res;
		}
		System.out.println(sum);
	}
}

(6)P2440 木材加工
这道题和砍树那道题几乎就是一个题,都是二分出一个答案,然后用一个函数验证这个答案是否可行,可行就在数组的右边区间继续二分答案,不可行就在数组左边二分答案。这里注意下1长度都切不出来,直接特判0.

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner sc=new Scanner(System.in);
		int n=sc.nextInt(),m=sc.nextInt();
		int[] arr=new int[n];
		for(int i=0;i<n;i++) {
			arr[i]=sc.nextInt();
		}
		int l=0,r=1000000000,mid,sum,max=0;
		while(l<=r) {
			mid=(l+r)/2;
			if(mid==0) {
				System.out.println(0);
				return;
			}
			sum=0;
			for(int i=0;i<n;i++) {
				sum+=arr[i]/mid;
			}
			if(sum>=m) {
				max=mid;
				l=mid+1;
			}
			else {
				r=mid-1;
			}
		}
		System.out.println(max);
	}
}

(7)P2678 跳石头
这道题也是用二分去搜索最长跳跃的距离,但在判断这个距离是否符合要求的时候需要思考一下如何判断:我们先用一个数组把所有的石头包括起点和终点的石头存下来,然后每当我们选定一个跳跃的距离,我们从第一个石头开始,它和它前面的一块石头的距离,是否大于我们跳跃的距离,如果满足,那么我们继续从这块石头跳,下一次的比较就是和这块石头进行比较距离。如果不满足,那么我们只好移除掉脚下这块石头,使得他可以跳更远的石头来满足我们选定的跳跃距离。最后判断我们移除的石头数量是否小于它给定的,来进行下次二分搜索跳跃距离。

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner sc=new Scanner(System.in);
		int n=sc.nextInt(),m=sc.nextInt(),k=sc.nextInt();
		int[] arr=new int[m+2];
		for(int i=1;i<=m;i++) {
			arr[i]=sc.nextInt();
		}
		arr[m+1]=n;
		int l=1,r=100000000,mid,ans,last,max=0;
		while(l<=r) {
			mid=(l+r)/2;
			ans=0;last=0;
			for(int i=1;i<=m+1;i++) {
				if(arr[i]-last<mid) {
					ans++;
				}
				else {
					last=arr[i];
				}
			}
			if(ans<=k) {
				max=mid;
				l=mid+1;
			}
			else {
				r=mid-1;
			}
		}
		System.out.println(max);
	}
}

(8)P3853 [TJOI2007]路标设置
和上题其实意思差不多,不过上题是最短的跳跃距离最长,这题是最长的公里距离最短,其实做法都是差不多的。我们二分选定一个长度,如果两个路标直接距离大于这个我们给定的长度,那么就要在中间加设路标,直到路标之间都小于等于我们给定的长度。最后判断我们加设路标的数量是否满足题目。

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner sc=new Scanner(System.in);
		int n=sc.nextInt(),m=sc.nextInt(),k=sc.nextInt();
		int[] arr=new int[m];
		for(int i=0;i<m;i++) {
			arr[i]=sc.nextInt();
		}
		int l=0,r=n,mid,sum,last,max=0;
		while(l<=r) {
			mid=(l+r)/2;sum=0;last=0;
			for(int i=1;i<m;i++) {
				while(arr[i]-last>mid) {
					last+=mid;
					sum++;
				}
				last=arr[i];
			}
			if(sum<=k) {
				max=mid;
				r=mid-1;
			}
			else {
				l=mid+1;
			}
		}
		System.out.println(max);
	}
}

(9)P1182 数列分段 Section II
这道题我们二分搜索最大值,每次选定一个最大值,然后判断所有数段进行分段,如果分段的数量小于等于题目给定的分段数,说明这个最大值可以再小,如果分段大于我们分的段,那么这个最大值应该取大些。


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;

public class Main {
	static int[] arr;
	static int n,m;
	public static void main(String[] args) throws IOException {
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		String[] s=br.readLine().split(" ");
		n=Integer.valueOf(s[0]);
		m=Integer.valueOf(s[1]);
		arr=new int[n];
		s=br.readLine().split(" ");
		int l=0,r=0;
		for(int i=0;i<n;i++) {
			arr[i]=Integer.valueOf(s[i]);
			r+=arr[i];
			l=Math.max(l, arr[i]);
		}
		int max=0;
		while(l<=r) {
			int mid=(l+r)/2;
			if(judge(mid)) {
				l=mid+1;
			}
			else {
				r=mid-1;
			}
		}
		System.out.println(l);
	}
	public static boolean judge(int k) {
		int total=0,tim=1;
		for(int i=0;i<n;i++){
	        if(total+arr[i]<=k)
	        	total+=arr[i];
	        else {
	        	total=arr[i];
	        	tim++;
	        }
	    }
	    return tim>m;
	}
}

(10)P1163 银行贷款

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值